Compare commits
346 Commits
paris-ci/p
...
paris-ci/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a51f9dd23 | ||
|
|
c34612dd71 | ||
|
|
6f5614373a | ||
|
|
2e12746c70 | ||
|
|
5ef72e4f70 | ||
|
|
7e18d30820 | ||
|
|
923a6a885d | ||
|
|
b28893aa36 | ||
|
|
6e41bd2219 | ||
|
|
773ad6f5bf | ||
|
|
de0e8ef108 | ||
|
|
64ee792391 | ||
|
|
22de755059 | ||
|
|
fa7f8efc8e | ||
|
|
9d1df65af3 | ||
|
|
3ce86f6cde | ||
|
|
31e3e99c2b | ||
|
|
cc90d312f5 | ||
|
|
75f052b8c9 | ||
|
|
c8cdb275c5 | ||
|
|
406f0ffe04 | ||
|
|
a4acbd2e08 | ||
|
|
6bcc717e63 | ||
|
|
86618f42a6 | ||
|
|
9d474b92f6 | ||
|
|
96ac8c1a4f | ||
|
|
cf4d2e23a2 | ||
|
|
45d498c1b7 | ||
|
|
7b87d2dec0 | ||
|
|
41d22f4312 | ||
|
|
12dcc7c44b | ||
|
|
b12fe223b2 | ||
|
|
a2a7b0f076 | ||
|
|
b2ac327bd8 | ||
|
|
f485f1b612 | ||
|
|
29b808d33f | ||
|
|
516675dd2e | ||
|
|
932efa1edc | ||
|
|
059ec161f8 | ||
|
|
e7821be4aa | ||
|
|
4aafa39e8c | ||
|
|
d2ea33e5e9 | ||
|
|
2f2c39ed22 | ||
|
|
efec816de2 | ||
|
|
dd7d4b8e7f | ||
|
|
2d8f299b6b | ||
|
|
3382d2e9e8 | ||
|
|
539577a2dd | ||
|
|
4f8e67998a | ||
|
|
848d752388 | ||
|
|
78598d59d7 | ||
|
|
ef79305961 | ||
|
|
c6a6c6af85 | ||
|
|
9a61a5a063 | ||
|
|
565b41b0b2 | ||
|
|
835432d161 | ||
|
|
f586f4dfbd | ||
|
|
769db38401 | ||
|
|
c82739a3be | ||
|
|
8306b9f6af | ||
|
|
490bbffc93 | ||
|
|
9d4fa0341e | ||
|
|
cff9ca0288 | ||
|
|
9dd86bbcb3 | ||
|
|
8bbb8f6db9 | ||
|
|
d8b06ca7f2 | ||
|
|
3561ce9d5a | ||
|
|
ae01a96bef | ||
|
|
5ef37923de | ||
|
|
61abb43b69 | ||
|
|
73f953eac5 | ||
|
|
400936df69 | ||
|
|
cdf46127ae | ||
|
|
851dfc3c75 | ||
|
|
b8898c7788 | ||
|
|
d17551f51f | ||
|
|
4a6670c062 | ||
|
|
d7a4230007 | ||
|
|
2e52059555 | ||
|
|
49cf959784 | ||
|
|
91652e3b60 | ||
|
|
9db8698748 | ||
|
|
9727b56503 | ||
|
|
e46d974c8a | ||
|
|
d09993d7e7 | ||
|
|
69f578abdc | ||
|
|
1b5c206279 | ||
|
|
d2dd31de63 | ||
|
|
d0c295b595 | ||
|
|
e1e3e298b5 | ||
|
|
4a72201617 | ||
|
|
ea2d972666 | ||
|
|
6268cad402 | ||
|
|
55f79ed096 | ||
|
|
4065014794 | ||
|
|
8d80259a80 | ||
|
|
311eac97b0 | ||
|
|
d5033b04a2 | ||
|
|
7592300535 | ||
|
|
166152647c | ||
|
|
6c36df6c11 | ||
|
|
fbc4a51c35 | ||
|
|
9246bbc8e3 | ||
|
|
fa5a2188bb | ||
|
|
5390caa67d | ||
|
|
745cf541ea | ||
|
|
ef32f6d882 | ||
|
|
b6590d7f56 | ||
|
|
b5a717fb80 | ||
|
|
f4d5fcc8f9 | ||
|
|
1d2eaf8526 | ||
|
|
f3cb197429 | ||
|
|
d4c683738d | ||
|
|
489e5f3288 | ||
|
|
63434fbfd9 | ||
|
|
68453c7bed | ||
|
|
b73f02b9c3 | ||
|
|
0df3f51a0b | ||
|
|
17f0b59c76 | ||
|
|
28ed599345 | ||
|
|
e79a648987 | ||
|
|
79bae47992 | ||
|
|
8fdd1f0d8f | ||
|
|
3b4c6269be | ||
|
|
27debe18ca | ||
|
|
8ac5cdc314 | ||
|
|
6b6bcb92e6 | ||
|
|
f7a3ea90b8 | ||
|
|
c4ee9dcafa | ||
|
|
529fad6fec | ||
|
|
36b9bc8ee3 | ||
|
|
6587b5c7ea | ||
|
|
feae059c68 | ||
|
|
1e17b7fcea | ||
|
|
fda543c844 | ||
|
|
08a4db3961 | ||
|
|
6e6c8a7b28 | ||
|
|
f631ed22b6 | ||
|
|
a9d9f496f0 | ||
|
|
dc9c224b54 | ||
|
|
1c40d43fd1 | ||
|
|
66871f329e | ||
|
|
1279510194 | ||
|
|
4fca699810 | ||
|
|
5e800a726e | ||
|
|
1418464813 | ||
|
|
1c63816cc0 | ||
|
|
ec32b71ff9 | ||
|
|
c628224403 | ||
|
|
58ca9e9932 | ||
|
|
0cc67e58ed | ||
|
|
035d110837 | ||
|
|
b87d306a70 | ||
|
|
e795d341e7 | ||
|
|
f72350199d | ||
|
|
b640493300 | ||
|
|
2de0398d66 | ||
|
|
e2250d402e | ||
|
|
2cb5ce981e | ||
|
|
41f3998a08 | ||
|
|
13a47dfe6e | ||
|
|
8b148afabd | ||
|
|
3a451c9c65 | ||
|
|
655bf25cc8 | ||
|
|
6beef898c6 | ||
|
|
658b61d468 | ||
|
|
06a371e80a | ||
|
|
608e3b5b6c | ||
|
|
d08725b0f0 | ||
|
|
3ad95f3746 | ||
|
|
56f800de9c | ||
|
|
8db79d2579 | ||
|
|
8851e03a6d | ||
|
|
60d82cf908 | ||
|
|
ecf239d2a2 | ||
|
|
0d3bd3083c | ||
|
|
731a8816bb | ||
|
|
dac0267e28 | ||
|
|
13251da8ce | ||
|
|
9d25bb454b | ||
|
|
0112c2819f | ||
|
|
906c13d4f0 | ||
|
|
a053f77275 | ||
|
|
51b02f2568 | ||
|
|
ca9b371982 | ||
|
|
41e2d3c637 | ||
|
|
154c90ef59 | ||
|
|
1472e9ed7c | ||
|
|
85f3e11ef1 | ||
|
|
67026809a8 | ||
|
|
dd8168f902 | ||
|
|
96b9a0e09d | ||
|
|
c059d43e98 | ||
|
|
78aea51f50 | ||
|
|
b47133dfb2 | ||
|
|
e4750c7105 | ||
|
|
fc51736b34 | ||
|
|
23852404ed | ||
|
|
980d6abbea | ||
|
|
bc75945088 | ||
|
|
dd5fc656d9 | ||
|
|
8675a18185 | ||
|
|
a0e5e062c9 | ||
|
|
07483297ad | ||
|
|
48eb981344 | ||
|
|
feed302269 | ||
|
|
262a50196d | ||
|
|
15eb3d2e5d | ||
|
|
0faf4c8e2b | ||
|
|
5b8be9a772 | ||
|
|
834e23dc00 | ||
|
|
26e68b31ef | ||
|
|
f14e584304 | ||
|
|
e2624b9a31 | ||
|
|
f153154b7a | ||
|
|
1a4e73d599 | ||
|
|
0aa825557d | ||
|
|
8fb998b599 | ||
|
|
826ce101fd | ||
|
|
b4e39668fb | ||
|
|
af8742a911 | ||
|
|
8df35c83a9 | ||
|
|
74e1ab09a0 | ||
|
|
03cd6ff433 | ||
|
|
99b8ae35ba | ||
|
|
cb2363f0fd | ||
|
|
88d825a803 | ||
|
|
e0a9365d61 | ||
|
|
717e723a36 | ||
|
|
1ca5b7b8b2 | ||
|
|
3cb539d91b | ||
|
|
5a7cfb3ce6 | ||
|
|
c1c6457598 | ||
|
|
c2acb0a114 | ||
|
|
feb0f7f29d | ||
|
|
7598865609 | ||
|
|
f40f80c0dc | ||
|
|
49f8073262 | ||
|
|
750ba88f2c | ||
|
|
074f34a5fa | ||
|
|
7d9074db8a | ||
|
|
4152819a3c | ||
|
|
5d798aa5e6 | ||
|
|
828e47d83f | ||
|
|
23a69144b6 | ||
|
|
d047cebc35 | ||
|
|
c748e4bce5 | ||
|
|
d1dc41ec2f | ||
|
|
097b6064f1 | ||
|
|
17268c3368 | ||
|
|
6a553b2347 | ||
|
|
93cc1bdd79 | ||
|
|
8b4dd34328 | ||
|
|
3d0dd5bc1b | ||
|
|
0b577fa209 | ||
|
|
be5603141e | ||
|
|
bba4d6c4e4 | ||
|
|
9f981e718b | ||
|
|
88620d052a | ||
|
|
8760b01e76 | ||
|
|
12e90f9c6d | ||
|
|
a8db8546db | ||
|
|
0fae0b4995 | ||
|
|
7ca90874b9 | ||
|
|
b7b75e2b1f | ||
|
|
ffa0b26b82 | ||
|
|
1059c02df7 | ||
|
|
d7ed884593 | ||
|
|
a3d7e06f25 | ||
|
|
982140b5f7 | ||
|
|
69c400d813 | ||
|
|
9ac459b5d3 | ||
|
|
4f0e907e44 | ||
|
|
812bfbe6f9 | ||
|
|
64b48431b4 | ||
|
|
30605e6f4f | ||
|
|
2d597e310b | ||
|
|
d001b9d0ee | ||
|
|
ed6c061d69 | ||
|
|
12e3eba011 | ||
|
|
c1f1c67eed | ||
|
|
cd4b0904db | ||
|
|
d8075d5412 | ||
|
|
157caaec7c | ||
|
|
62dad0f7bf | ||
|
|
1aeec34f84 | ||
|
|
44d1d29708 | ||
|
|
ea1d423904 | ||
|
|
6f3b8072d6 | ||
|
|
2d7c709235 | ||
|
|
a372aadb2d | ||
|
|
53ef89c29f | ||
|
|
c6a69062a8 | ||
|
|
62b024803a | ||
|
|
b1a355394f | ||
|
|
39a674ddee | ||
|
|
abac04b759 | ||
|
|
7d0bd7ed20 | ||
|
|
7601d6cec3 | ||
|
|
7386a971f8 | ||
|
|
2beee8be14 | ||
|
|
eda02c2e91 | ||
|
|
485542c480 | ||
|
|
a2b10a08b9 | ||
|
|
55c7de82d3 | ||
|
|
f9bccabac5 | ||
|
|
d9adf4d35d | ||
|
|
f7d551953b | ||
|
|
6b1d46a1ea | ||
|
|
e96df33ce0 | ||
|
|
e3a66bcccc | ||
|
|
039bb9f871 | ||
|
|
2ce44f7bd6 | ||
|
|
3c5c5fc274 | ||
|
|
d1a2ee4620 | ||
|
|
a75cd93acc | ||
|
|
5acea453cc | ||
|
|
cd6b453cb3 | ||
|
|
4566b64d77 | ||
|
|
b1836c5577 | ||
|
|
75477b2995 | ||
|
|
2cd2d1d3ee | ||
|
|
f7b0ed7b12 | ||
|
|
b59ec318c0 | ||
|
|
6ce1c537d4 | ||
|
|
caa9512a8a | ||
|
|
47e6a754e4 | ||
|
|
8b7e5a50b4 | ||
|
|
1a3422dccc | ||
|
|
233d10649c | ||
|
|
76c9e390f1 | ||
|
|
cbe7a1b3a2 | ||
|
|
b2c9c26841 | ||
|
|
20dd632722 | ||
|
|
3c2cf06e46 | ||
|
|
dbb135b81a | ||
|
|
d30fea5b0d | ||
|
|
f27e2e073f | ||
|
|
0bc5f276a7 | ||
|
|
1c640ad72b | ||
|
|
f0c76a13d3 | ||
|
|
a0e1d1e25f | ||
|
|
04573c3c06 | ||
|
|
f6ea03230e | ||
|
|
c251c51cb1 | ||
|
|
9181bf046b |
@@ -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
|
||||||
|
|||||||
114
README.ja.rst
114
README.ja.rst
@@ -1,114 +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。
|
|
||||||
- 適切なレート制限処理
|
|
||||||
- Discord APIによってサポートされているものを100%カバー。
|
|
||||||
- メモリと速度の両方を最適化。
|
|
||||||
|
|
||||||
インストール
|
|
||||||
-------------
|
|
||||||
|
|
||||||
**Python 3.5.3 以降のバージョンが必須です**
|
|
||||||
|
|
||||||
完全な音声サポートなしでライブラリをインストールする場合は次のコマンドを実行してください:
|
|
||||||
|
|
||||||
.. 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>`_
|
|
||||||
55
README.rst
55
README.rst
@@ -1,57 +1,64 @@
|
|||||||
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
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
- Modern Pythonic API using ``async`` and ``await``.
|
- Modern Pythonic API using ``async`` and ``await``.
|
||||||
- Proper rate limit handling.
|
- Proper rate limit handling.
|
||||||
- 100% coverage of the supported Discord API.
|
|
||||||
- Optimised in both speed and memory.
|
- Optimised in both speed and memory.
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
|
||||||
**Python 3.5.3 or higher is required**
|
**Python 3.8 or higher is required**
|
||||||
|
|
||||||
To install the library without full voice support, you can just run the following command:
|
To install the library without full voice support, you can just run the following command:
|
||||||
|
|
||||||
.. 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]
|
||||||
|
|
||||||
@@ -109,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>`_
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ __version__ = '2.0.0a'
|
|||||||
|
|
||||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import NamedTuple, Literal
|
||||||
|
|
||||||
from .client import *
|
from .client import *
|
||||||
from .appinfo import *
|
from .appinfo import *
|
||||||
@@ -60,8 +60,15 @@ from .interactions import *
|
|||||||
from .components import *
|
from .components import *
|
||||||
from .threads import *
|
from .threads import *
|
||||||
|
|
||||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial')
|
|
||||||
|
|
||||||
version_info = VersionInfo(major=2, minor=0, micro=0, releaselevel='alpha', serial=0)
|
class VersionInfo(NamedTuple):
|
||||||
|
major: int
|
||||||
|
minor: int
|
||||||
|
micro: int
|
||||||
|
releaselevel: Literal["alpha", "beta", "candidate", "final"]
|
||||||
|
serial: int
|
||||||
|
|
||||||
|
|
||||||
|
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())
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
333
discord/abc.py
333
discord/abc.py
@@ -26,7 +26,21 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Dict, List, Mapping, Optional, TYPE_CHECKING, Protocol, Type, TypeVar, Union, overload, runtime_checkable
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Protocol,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
overload,
|
||||||
|
runtime_checkable,
|
||||||
|
)
|
||||||
|
|
||||||
from .iterators import HistoryIterator
|
from .iterators import HistoryIterator
|
||||||
from .context_managers import Typing
|
from .context_managers import Typing
|
||||||
@@ -38,6 +52,7 @@ from .role import Role
|
|||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
from .file import File
|
from .file import File
|
||||||
from .voice_client import VoiceClient, VoiceProtocol
|
from .voice_client import VoiceClient, VoiceProtocol
|
||||||
|
from .sticker import GuildSticker, StickerItem
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@@ -54,6 +69,7 @@ T = TypeVar('T', bound=VoiceProtocol)
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from .client import Client
|
||||||
from .user import ClientUser
|
from .user import ClientUser
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
from .state import ConnectionState
|
from .state import ConnectionState
|
||||||
@@ -61,17 +77,27 @@ if TYPE_CHECKING:
|
|||||||
from .member import Member
|
from .member import Member
|
||||||
from .channel import CategoryChannel
|
from .channel import CategoryChannel
|
||||||
from .embeds import Embed
|
from .embeds import Embed
|
||||||
from .message import Message, MessageReference
|
from .message import Message, MessageReference, PartialMessage
|
||||||
|
from .channel import TextChannel, DMChannel, GroupChannel, PartialMessageable
|
||||||
|
from .threads import Thread
|
||||||
from .enums import InviteTarget
|
from .enums import InviteTarget
|
||||||
from .ui.view import View
|
from .ui.view import View
|
||||||
|
from .types.channel import (
|
||||||
|
PermissionOverwrite as PermissionOverwritePayload,
|
||||||
|
Channel as ChannelPayload,
|
||||||
|
GuildChannel as GuildChannelPayload,
|
||||||
|
OverwriteType,
|
||||||
|
)
|
||||||
|
|
||||||
|
PartialMessageableChannel = Union[TextChannel, Thread, DMChannel, PartialMessageable]
|
||||||
|
MessageableChannel = Union[PartialMessageableChannel, GroupChannel]
|
||||||
SnowflakeTime = Union["Snowflake", datetime]
|
SnowflakeTime = Union["Snowflake", datetime]
|
||||||
|
|
||||||
MISSING = utils.MISSING
|
MISSING = utils.MISSING
|
||||||
|
|
||||||
|
|
||||||
class _Undefined:
|
class _Undefined:
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return 'see-below'
|
return 'see-below'
|
||||||
|
|
||||||
|
|
||||||
@@ -97,10 +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):
|
||||||
@@ -172,13 +194,13 @@ class _Overwrites:
|
|||||||
ROLE = 0
|
ROLE = 0
|
||||||
MEMBER = 1
|
MEMBER = 1
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, data: PermissionOverwritePayload):
|
||||||
self.id = kwargs.pop('id')
|
self.id: int = int(data['id'])
|
||||||
self.allow = int(kwargs.pop('allow', 0))
|
self.allow: int = int(data.get('allow', 0))
|
||||||
self.deny = int(kwargs.pop('deny', 0))
|
self.deny: int = int(data.get('deny', 0))
|
||||||
self.type = kwargs.pop('type')
|
self.type: OverwriteType = data['type']
|
||||||
|
|
||||||
def _asdict(self):
|
def _asdict(self) -> PermissionOverwritePayload:
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'allow': str(self.allow),
|
'allow': str(self.allow),
|
||||||
@@ -208,11 +230,6 @@ class GuildChannel:
|
|||||||
|
|
||||||
This ABC must also implement :class:`~discord.abc.Snowflake`.
|
This ABC must also implement :class:`~discord.abc.Snowflake`.
|
||||||
|
|
||||||
Note
|
|
||||||
----
|
|
||||||
This ABC is not decorated with :func:`typing.runtime_checkable`, so will fail :func:`isinstance`/:func:`issubclass`
|
|
||||||
checks.
|
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
@@ -230,7 +247,10 @@ class GuildChannel:
|
|||||||
name: str
|
name: str
|
||||||
guild: Guild
|
guild: Guild
|
||||||
type: ChannelType
|
type: ChannelType
|
||||||
|
position: int
|
||||||
|
category_id: Optional[int]
|
||||||
_state: ConnectionState
|
_state: ConnectionState
|
||||||
|
_overwrites: List[_Overwrites]
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
@@ -254,13 +274,13 @@ class GuildChannel:
|
|||||||
lock_permissions: bool = False,
|
lock_permissions: bool = False,
|
||||||
*,
|
*,
|
||||||
reason: Optional[str],
|
reason: Optional[str],
|
||||||
):
|
) -> None:
|
||||||
if position < 0:
|
if position < 0:
|
||||||
raise InvalidArgument('Channel position cannot be less than 0.')
|
raise InvalidArgument('Channel position cannot be less than 0.')
|
||||||
|
|
||||||
http = self._state.http
|
http = self._state.http
|
||||||
bucket = self._sorting_bucket
|
bucket = self._sorting_bucket
|
||||||
channels = [c for c in self.guild.channels if c._sorting_bucket == bucket]
|
channels: List[GuildChannel] = [c for c in self.guild.channels if c._sorting_bucket == bucket]
|
||||||
|
|
||||||
channels.sort(key=lambda c: c.position)
|
channels.sort(key=lambda c: c.position)
|
||||||
|
|
||||||
@@ -277,17 +297,14 @@ class GuildChannel:
|
|||||||
|
|
||||||
payload = []
|
payload = []
|
||||||
for index, c in enumerate(channels):
|
for index, c in enumerate(channels):
|
||||||
d = {'id': c.id, 'position': index}
|
d: Dict[str, Any] = {'id': c.id, 'position': index}
|
||||||
if parent_id is not _undefined and c.id == self.id:
|
if parent_id is not _undefined and c.id == self.id:
|
||||||
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
|
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
|
||||||
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, reason):
|
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:
|
||||||
@@ -322,12 +339,14 @@ class GuildChannel:
|
|||||||
if parent_id is not _undefined:
|
if parent_id is not _undefined:
|
||||||
if lock_permissions:
|
if lock_permissions:
|
||||||
category = self.guild.get_channel(parent_id)
|
category = self.guild.get_channel(parent_id)
|
||||||
|
if category:
|
||||||
options['permission_overwrites'] = [c._asdict() for c in category._overwrites]
|
options['permission_overwrites'] = [c._asdict() for c in category._overwrites]
|
||||||
options['parent_id'] = parent_id
|
options['parent_id'] = parent_id
|
||||||
elif lock_permissions and self.category_id is not None:
|
elif lock_permissions and self.category_id is not None:
|
||||||
# if we're syncing permissions on a pre-existing channel category without changing it
|
# if we're syncing permissions on a pre-existing channel category without changing it
|
||||||
# we need to update the permissions to point to the pre-existing category
|
# we need to update the permissions to point to the pre-existing category
|
||||||
category = self.guild.get_channel(self.category_id)
|
category = self.guild.get_channel(self.category_id)
|
||||||
|
if category:
|
||||||
options['permission_overwrites'] = [c._asdict() for c in category._overwrites]
|
options['permission_overwrites'] = [c._asdict() for c in category._overwrites]
|
||||||
else:
|
else:
|
||||||
await self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason)
|
await self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason)
|
||||||
@@ -364,22 +383,21 @@ 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):
|
def _fill_overwrites(self, data: GuildChannelPayload) -> None:
|
||||||
self._overwrites = []
|
self._overwrites = []
|
||||||
everyone_index = 0
|
everyone_index = 0
|
||||||
everyone_id = self.guild.id
|
everyone_id = self.guild.id
|
||||||
|
|
||||||
for index, overridden in enumerate(data.get('permission_overwrites', [])):
|
for index, overridden in enumerate(data.get('permission_overwrites', [])):
|
||||||
overridden_id = int(overridden.pop('id'))
|
overwrite = _Overwrites(overridden)
|
||||||
self._overwrites.append(_Overwrites(id=overridden_id, **overridden))
|
self._overwrites.append(overwrite)
|
||||||
|
|
||||||
if overridden['type'] == _Overwrites.MEMBER:
|
if overwrite.type == _Overwrites.MEMBER:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if overridden_id == everyone_id:
|
if overwrite.id == everyone_id:
|
||||||
# the @everyone role is not guaranteed to be the first one
|
# the @everyone role is not guaranteed to be the first one
|
||||||
# in the list of permission overwrites, however the permission
|
# in the list of permission overwrites, however the permission
|
||||||
# resolution code kind of requires that it is the first one in
|
# resolution code kind of requires that it is the first one in
|
||||||
@@ -449,7 +467,7 @@ class GuildChannel:
|
|||||||
return PermissionOverwrite()
|
return PermissionOverwrite()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def overwrites(self) -> Mapping[Union[Role, Member], PermissionOverwrite]:
|
def overwrites(self) -> Dict[Union[Role, Member], PermissionOverwrite]:
|
||||||
"""Returns all of the channel's overwrites.
|
"""Returns all of the channel's overwrites.
|
||||||
|
|
||||||
This is returned as a dictionary where the key contains the target which
|
This is returned as a dictionary where the key contains the target which
|
||||||
@@ -458,7 +476,7 @@ class GuildChannel:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
Mapping[Union[:class:`~discord.Role`, :class:`~discord.Member`], :class:`~discord.PermissionOverwrite`]
|
Dict[Union[:class:`~discord.Role`, :class:`~discord.Member`], :class:`~discord.PermissionOverwrite`]
|
||||||
The channel's permission overwrites.
|
The channel's permission overwrites.
|
||||||
"""
|
"""
|
||||||
ret = {}
|
ret = {}
|
||||||
@@ -488,7 +506,7 @@ class GuildChannel:
|
|||||||
|
|
||||||
If there is no category then this is ``None``.
|
If there is no category then this is ``None``.
|
||||||
"""
|
"""
|
||||||
return self.guild.get_channel(self.category_id)
|
return self.guild.get_channel(self.category_id) # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def permissions_synced(self) -> bool:
|
def permissions_synced(self) -> bool:
|
||||||
@@ -499,6 +517,9 @@ class GuildChannel:
|
|||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
"""
|
"""
|
||||||
|
if self.category_id is None:
|
||||||
|
return False
|
||||||
|
|
||||||
category = self.guild.get_channel(self.category_id)
|
category = self.guild.get_channel(self.category_id)
|
||||||
return bool(category and category.overwrites == self.overwrites)
|
return bool(category and category.overwrites == self.overwrites)
|
||||||
|
|
||||||
@@ -517,6 +538,7 @@ class GuildChannel:
|
|||||||
someone with that role would have, which is essentially:
|
someone with that role would have, which is essentially:
|
||||||
|
|
||||||
- The default role permissions
|
- The default role permissions
|
||||||
|
- The permissions of the role used as a parameter
|
||||||
- The default role permission overwrites
|
- The default role permission overwrites
|
||||||
- The permission overwrites of the role used as a parameter
|
- The permission overwrites of the role used as a parameter
|
||||||
|
|
||||||
@@ -558,24 +580,26 @@ class GuildChannel:
|
|||||||
|
|
||||||
# Handle the role case first
|
# Handle the role case first
|
||||||
if isinstance(obj, Role):
|
if isinstance(obj, Role):
|
||||||
|
base.value |= obj._permissions
|
||||||
|
|
||||||
|
if base.administrator:
|
||||||
|
return Permissions.all()
|
||||||
|
|
||||||
|
# Apply @everyone allow/deny first since it's special
|
||||||
|
try:
|
||||||
|
maybe_everyone = self._overwrites[0]
|
||||||
|
if maybe_everyone.id == self.guild.id:
|
||||||
|
base.handle_overwrite(allow=maybe_everyone.allow, deny=maybe_everyone.deny)
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
if obj.is_default():
|
if obj.is_default():
|
||||||
|
return base
|
||||||
|
|
||||||
overwrite = utils.get(self._overwrites, type=_Overwrites.ROLE, id=obj.id)
|
overwrite = utils.get(self._overwrites, type=_Overwrites.ROLE, id=obj.id)
|
||||||
if overwrite is not None:
|
if overwrite is not None:
|
||||||
base.handle_overwrite(overwrite.allow, overwrite.deny)
|
base.handle_overwrite(overwrite.allow, overwrite.deny)
|
||||||
return base
|
|
||||||
|
|
||||||
denies = 0
|
|
||||||
allows = 0
|
|
||||||
guild_id = self.guild.id
|
|
||||||
for overwrite in self._overwrites:
|
|
||||||
if not overwrite.is_role():
|
|
||||||
continue
|
|
||||||
|
|
||||||
if overwrite.id in (obj.id, guild_id):
|
|
||||||
denies |= overwrite.deny
|
|
||||||
allows |= overwrite.allow
|
|
||||||
|
|
||||||
base.handle_overwrite(allows, denies)
|
|
||||||
return base
|
return base
|
||||||
|
|
||||||
roles = obj._roles
|
roles = obj._roles
|
||||||
@@ -679,14 +703,7 @@ class GuildChannel:
|
|||||||
) -> None:
|
) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
async def set_permissions(
|
async def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions):
|
||||||
self,
|
|
||||||
target,
|
|
||||||
*,
|
|
||||||
overwrite=_undefined,
|
|
||||||
reason=None,
|
|
||||||
**permissions
|
|
||||||
):
|
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
Sets the channel specific permission overwrites for a target in the
|
Sets the channel specific permission overwrites for a target in the
|
||||||
@@ -801,7 +818,7 @@ class GuildChannel:
|
|||||||
obj = cls(state=self._state, guild=self.guild, data=data)
|
obj = cls(state=self._state, guild=self.guild, data=data)
|
||||||
|
|
||||||
# temporarily add it to the cache
|
# temporarily add it to the cache
|
||||||
self.guild._channels[obj.id] = obj
|
self.guild._channels[obj.id] = obj # type: ignore
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
async def clone(self: GCH, *, name: Optional[str] = None, reason: Optional[str] = None) -> GCH:
|
async def clone(self: GCH, *, name: Optional[str] = None, reason: Optional[str] = None) -> GCH:
|
||||||
@@ -956,6 +973,7 @@ class GuildChannel:
|
|||||||
bucket = self._sorting_bucket
|
bucket = self._sorting_bucket
|
||||||
parent_id = kwargs.get('category', MISSING)
|
parent_id = kwargs.get('category', MISSING)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
channels: List[GuildChannel]
|
||||||
if parent_id not in (MISSING, None):
|
if parent_id not in (MISSING, None):
|
||||||
parent_id = parent_id.id
|
parent_id = parent_id.id
|
||||||
channels = [
|
channels = [
|
||||||
@@ -1017,7 +1035,7 @@ class GuildChannel:
|
|||||||
unique: bool = True,
|
unique: bool = True,
|
||||||
target_type: Optional[InviteTarget] = None,
|
target_type: Optional[InviteTarget] = None,
|
||||||
target_user: Optional[User] = None,
|
target_user: Optional[User] = None,
|
||||||
target_application_id: Optional[int] = None
|
target_application_id: Optional[int] = None,
|
||||||
) -> Invite:
|
) -> Invite:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@@ -1043,7 +1061,7 @@ class GuildChannel:
|
|||||||
invite.
|
invite.
|
||||||
reason: Optional[:class:`str`]
|
reason: Optional[:class:`str`]
|
||||||
The reason for creating this invite. Shows up on the audit log.
|
The reason for creating this invite. Shows up on the audit log.
|
||||||
target_type: Optional[:class:`InviteTarget`]
|
target_type: Optional[:class:`.InviteTarget`]
|
||||||
The type of target for the voice channel invite, if any.
|
The type of target for the voice channel invite, if any.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
@@ -1081,7 +1099,7 @@ class GuildChannel:
|
|||||||
unique=unique,
|
unique=unique,
|
||||||
target_type=target_type.value if target_type else None,
|
target_type=target_type.value if target_type else None,
|
||||||
target_user_id=target_user.id if target_user else None,
|
target_user_id=target_user.id if target_user else None,
|
||||||
target_application_id=target_application_id
|
target_application_id=target_application_id,
|
||||||
)
|
)
|
||||||
return Invite.from_incomplete(data=data, state=self._state)
|
return Invite.from_incomplete(data=data, state=self._state)
|
||||||
|
|
||||||
@@ -1111,7 +1129,7 @@ class GuildChannel:
|
|||||||
return [Invite(state=state, data=invite, channel=self, guild=guild) for invite in data]
|
return [Invite(state=state, data=invite, channel=self, guild=guild) for invite in data]
|
||||||
|
|
||||||
|
|
||||||
class Messageable(Protocol):
|
class Messageable:
|
||||||
"""An ABC that details the common operations on a model that can send messages.
|
"""An ABC that details the common operations on a model that can send messages.
|
||||||
|
|
||||||
The following implement this ABC:
|
The following implement this ABC:
|
||||||
@@ -1122,17 +1140,13 @@ class Messageable(Protocol):
|
|||||||
- :class:`~discord.User`
|
- :class:`~discord.User`
|
||||||
- :class:`~discord.Member`
|
- :class:`~discord.Member`
|
||||||
- :class:`~discord.ext.commands.Context`
|
- :class:`~discord.ext.commands.Context`
|
||||||
|
- :class:`~discord.Thread`
|
||||||
|
|
||||||
Note
|
|
||||||
----
|
|
||||||
This ABC is not decorated with :func:`typing.runtime_checkable`, so will fail :func:`isinstance`/:func:`issubclass`
|
|
||||||
checks.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
_state: ConnectionState
|
||||||
|
|
||||||
async def _get_channel(self):
|
async def _get_channel(self) -> MessageableChannel:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@@ -1143,10 +1157,11 @@ class Messageable(Protocol):
|
|||||||
tts: bool = ...,
|
tts: bool = ...,
|
||||||
embed: Embed = ...,
|
embed: Embed = ...,
|
||||||
file: File = ...,
|
file: File = ...,
|
||||||
delete_after: int = ...,
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
nonce: Union[str, int] = ...,
|
nonce: Union[str, int] = ...,
|
||||||
allowed_mentions: AllowedMentions = ...,
|
allowed_mentions: AllowedMentions = ...,
|
||||||
reference: Union[Message, MessageReference] = ...,
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
mention_author: bool = ...,
|
mention_author: bool = ...,
|
||||||
view: View = ...,
|
view: View = ...,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
@@ -1160,19 +1175,69 @@ class Messageable(Protocol):
|
|||||||
tts: bool = ...,
|
tts: bool = ...,
|
||||||
embed: Embed = ...,
|
embed: Embed = ...,
|
||||||
files: List[File] = ...,
|
files: List[File] = ...,
|
||||||
delete_after: int = ...,
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
nonce: Union[str, int] = ...,
|
nonce: Union[str, int] = ...,
|
||||||
allowed_mentions: AllowedMentions = ...,
|
allowed_mentions: AllowedMentions = ...,
|
||||||
reference: Union[Message, MessageReference] = ...,
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
mention_author: bool = ...,
|
mention_author: bool = ...,
|
||||||
view: View = ...,
|
view: View = ...,
|
||||||
) -> Message:
|
) -> Message:
|
||||||
...
|
...
|
||||||
|
|
||||||
async def send(self, content=None, *, tts=False, embed=None, file=None,
|
@overload
|
||||||
files=None, delete_after=None, nonce=None,
|
async def send(
|
||||||
allowed_mentions=None, reference=None,
|
self,
|
||||||
mention_author=None, view=None):
|
content: Optional[str] = ...,
|
||||||
|
*,
|
||||||
|
tts: bool = ...,
|
||||||
|
embeds: List[Embed] = ...,
|
||||||
|
file: File = ...,
|
||||||
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
|
nonce: Union[str, int] = ...,
|
||||||
|
allowed_mentions: AllowedMentions = ...,
|
||||||
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
|
mention_author: bool = ...,
|
||||||
|
view: View = ...,
|
||||||
|
) -> Message:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
async def send(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = ...,
|
||||||
|
*,
|
||||||
|
tts: bool = ...,
|
||||||
|
embeds: List[Embed] = ...,
|
||||||
|
files: List[File] = ...,
|
||||||
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
|
nonce: Union[str, int] = ...,
|
||||||
|
allowed_mentions: AllowedMentions = ...,
|
||||||
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
|
mention_author: bool = ...,
|
||||||
|
view: View = ...,
|
||||||
|
) -> Message:
|
||||||
|
...
|
||||||
|
|
||||||
|
async def send(
|
||||||
|
self,
|
||||||
|
content=None,
|
||||||
|
*,
|
||||||
|
tts=None,
|
||||||
|
embed=None,
|
||||||
|
embeds=None,
|
||||||
|
file=None,
|
||||||
|
files=None,
|
||||||
|
stickers=None,
|
||||||
|
delete_after=None,
|
||||||
|
nonce=None,
|
||||||
|
allowed_mentions=None,
|
||||||
|
reference=None,
|
||||||
|
mention_author=None,
|
||||||
|
view=None,
|
||||||
|
):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Sends a message to the destination with the content given.
|
Sends a message to the destination with the content given.
|
||||||
@@ -1186,12 +1251,14 @@ class Messageable(Protocol):
|
|||||||
parameter should be used with a :class:`list` of :class:`~discord.File` objects.
|
parameter should be used with a :class:`list` of :class:`~discord.File` objects.
|
||||||
**Specifying both parameters will lead to an exception**.
|
**Specifying both parameters will lead to an exception**.
|
||||||
|
|
||||||
If the ``embed`` parameter is provided, it must be of type :class:`~discord.Embed` and
|
To upload a single embed, the ``embed`` parameter should be used with a
|
||||||
it must be a rich embed type.
|
single :class:`~discord.Embed` object. To upload multiple embeds, the ``embeds``
|
||||||
|
parameter should be used with a :class:`list` of :class:`~discord.Embed` objects.
|
||||||
|
**Specifying both parameters will lead to an exception**.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
content: :class:`str`
|
content: Optional[:class:`str`]
|
||||||
The content of the message to send.
|
The content of the message to send.
|
||||||
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.
|
||||||
@@ -1218,7 +1285,7 @@ class Messageable(Protocol):
|
|||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
reference: Union[:class:`~discord.Message`, :class:`~discord.MessageReference`]
|
reference: Union[:class:`~discord.Message`, :class:`~discord.MessageReference`, :class:`~discord.PartialMessage`]
|
||||||
A reference to the :class:`~discord.Message` to which you are replying, this can be created using
|
A reference to the :class:`~discord.Message` to which you are replying, this can be created using
|
||||||
:meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. You can control
|
:meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. You can control
|
||||||
whether this mentions the author of the referenced message using the :attr:`~discord.AllowedMentions.replied_user`
|
whether this mentions the author of the referenced message using the :attr:`~discord.AllowedMentions.replied_user`
|
||||||
@@ -1232,6 +1299,12 @@ class Messageable(Protocol):
|
|||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
view: :class:`discord.ui.View`
|
view: :class:`discord.ui.View`
|
||||||
A Discord UI View to add to the message.
|
A Discord UI View to add to the message.
|
||||||
|
embeds: List[:class:`~discord.Embed`]
|
||||||
|
A list of embeds to upload. Must be a maximum of 10.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
stickers: Sequence[Union[:class:`~discord.GuildSticker`, :class:`~discord.StickerItem`]]
|
||||||
|
A list of stickers to upload. Must be a maximum of 3.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
@@ -1244,8 +1317,9 @@ class Messageable(Protocol):
|
|||||||
~discord.InvalidArgument
|
~discord.InvalidArgument
|
||||||
The ``files`` list is not of the appropriate size,
|
The ``files`` list is not of the appropriate size,
|
||||||
you specified both ``file`` and ``files``,
|
you specified both ``file`` and ``files``,
|
||||||
or the ``reference`` object is not a :class:`~discord.Message`
|
or you specified both ``embed`` and ``embeds``,
|
||||||
or :class:`~discord.MessageReference`.
|
or the ``reference`` object is not a :class:`~discord.Message`,
|
||||||
|
:class:`~discord.MessageReference` or :class:`~discord.PartialMessage`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
@@ -1256,9 +1330,21 @@ class Messageable(Protocol):
|
|||||||
channel = await self._get_channel()
|
channel = await self._get_channel()
|
||||||
state = self._state
|
state = self._state
|
||||||
content = str(content) if content is not None else None
|
content = str(content) if content is not None else None
|
||||||
|
|
||||||
|
if embed is not None and embeds is not None:
|
||||||
|
raise InvalidArgument('cannot pass both embed and embeds parameter to send()')
|
||||||
|
|
||||||
if embed is not None:
|
if embed is not None:
|
||||||
embed = embed.to_dict()
|
embed = embed.to_dict()
|
||||||
|
|
||||||
|
elif embeds is not None:
|
||||||
|
if len(embeds) > 10:
|
||||||
|
raise InvalidArgument('embeds parameter must be a list of up to 10 elements')
|
||||||
|
embeds = [embed.to_dict() for embed in embeds]
|
||||||
|
|
||||||
|
if stickers is not None:
|
||||||
|
stickers = [sticker.id for sticker in stickers]
|
||||||
|
|
||||||
if allowed_mentions is not None:
|
if allowed_mentions is not None:
|
||||||
if state.allowed_mentions is not None:
|
if state.allowed_mentions is not None:
|
||||||
allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict()
|
allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict()
|
||||||
@@ -1275,7 +1361,7 @@ class Messageable(Protocol):
|
|||||||
try:
|
try:
|
||||||
reference = reference.to_message_reference_dict()
|
reference = reference.to_message_reference_dict()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise InvalidArgument('reference parameter must be Message or MessageReference') from None
|
raise InvalidArgument('reference parameter must be Message, MessageReference, or PartialMessage') from None
|
||||||
|
|
||||||
if view:
|
if view:
|
||||||
if not hasattr(view, '__discord_ui_view__'):
|
if not hasattr(view, '__discord_ui_view__'):
|
||||||
@@ -1293,9 +1379,19 @@ class Messageable(Protocol):
|
|||||||
raise InvalidArgument('file parameter must be File')
|
raise InvalidArgument('file parameter must be File')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions,
|
data = await state.http.send_files(
|
||||||
content=content, tts=tts, embed=embed, nonce=nonce,
|
channel.id,
|
||||||
message_reference=reference, components=components)
|
files=[file],
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
content=content,
|
||||||
|
tts=tts,
|
||||||
|
embed=embed,
|
||||||
|
embeds=embeds,
|
||||||
|
nonce=nonce,
|
||||||
|
message_reference=reference,
|
||||||
|
stickers=stickers,
|
||||||
|
components=components,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
@@ -1306,16 +1402,35 @@ class Messageable(Protocol):
|
|||||||
raise InvalidArgument('files parameter must be a list of File')
|
raise InvalidArgument('files parameter must be a list of File')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
|
data = await state.http.send_files(
|
||||||
embed=embed, nonce=nonce, allowed_mentions=allowed_mentions,
|
channel.id,
|
||||||
message_reference=reference, components=components)
|
files=files,
|
||||||
|
content=content,
|
||||||
|
tts=tts,
|
||||||
|
embed=embed,
|
||||||
|
embeds=embeds,
|
||||||
|
nonce=nonce,
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
message_reference=reference,
|
||||||
|
stickers=stickers,
|
||||||
|
components=components,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
for f in files:
|
for f in files:
|
||||||
f.close()
|
f.close()
|
||||||
else:
|
else:
|
||||||
data = await state.http.send_message(channel.id, content, tts=tts, embed=embed,
|
data = await state.http.send_message(
|
||||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
channel.id,
|
||||||
message_reference=reference, components=components)
|
content,
|
||||||
|
tts=tts,
|
||||||
|
embed=embed,
|
||||||
|
embeds=embeds,
|
||||||
|
nonce=nonce,
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
message_reference=reference,
|
||||||
|
stickers=stickers,
|
||||||
|
components=components,
|
||||||
|
)
|
||||||
|
|
||||||
ret = state.create_message(channel=channel, data=data)
|
ret = state.create_message(channel=channel, data=data)
|
||||||
if view:
|
if view:
|
||||||
@@ -1325,7 +1440,7 @@ class Messageable(Protocol):
|
|||||||
await ret.delete(delay=delete_after)
|
await ret.delete(delay=delete_after)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def trigger_typing(self):
|
async def trigger_typing(self) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Triggers a *typing* indicator to the destination.
|
Triggers a *typing* indicator to the destination.
|
||||||
@@ -1336,7 +1451,7 @@ class Messageable(Protocol):
|
|||||||
channel = await self._get_channel()
|
channel = await self._get_channel()
|
||||||
await self._state.http.send_typing(channel.id)
|
await self._state.http.send_typing(channel.id)
|
||||||
|
|
||||||
def typing(self):
|
def typing(self) -> Typing:
|
||||||
"""Returns a context manager that allows you to type for an indefinite period of time.
|
"""Returns a context manager that allows you to type for an indefinite period of time.
|
||||||
|
|
||||||
This is useful for denoting long computations in your bot.
|
This is useful for denoting long computations in your bot.
|
||||||
@@ -1347,6 +1462,7 @@ class Messageable(Protocol):
|
|||||||
This means that both ``with`` and ``async with`` work with this.
|
This means that both ``with`` and ``async with`` work with this.
|
||||||
|
|
||||||
Example Usage: ::
|
Example Usage: ::
|
||||||
|
|
||||||
async with channel.typing():
|
async with channel.typing():
|
||||||
# simulate something heavy
|
# simulate something heavy
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
@@ -1356,7 +1472,7 @@ class Messageable(Protocol):
|
|||||||
"""
|
"""
|
||||||
return Typing(self)
|
return Typing(self)
|
||||||
|
|
||||||
async def fetch_message(self, id):
|
async def fetch_message(self, id: int, /) -> Message:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves a single :class:`~discord.Message` from the destination.
|
Retrieves a single :class:`~discord.Message` from the destination.
|
||||||
@@ -1385,7 +1501,7 @@ class Messageable(Protocol):
|
|||||||
data = await self._state.http.get_message(channel.id, id)
|
data = await self._state.http.get_message(channel.id, id)
|
||||||
return self._state.create_message(channel=channel, data=data)
|
return self._state.create_message(channel=channel, data=data)
|
||||||
|
|
||||||
async def pins(self):
|
async def pins(self) -> List[Message]:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves all messages that are currently pinned in the channel.
|
Retrieves all messages that are currently pinned in the channel.
|
||||||
@@ -1412,7 +1528,15 @@ class Messageable(Protocol):
|
|||||||
data = await state.http.pins_from(channel.id)
|
data = await state.http.pins_from(channel.id)
|
||||||
return [state.create_message(channel=channel, data=m) for m in data]
|
return [state.create_message(channel=channel, data=m) for m in data]
|
||||||
|
|
||||||
def history(self, *, limit=100, before=None, after=None, around=None, oldest_first=None):
|
def history(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
limit: Optional[int] = 100,
|
||||||
|
before: Optional[SnowflakeTime] = None,
|
||||||
|
after: Optional[SnowflakeTime] = None,
|
||||||
|
around: Optional[SnowflakeTime] = None,
|
||||||
|
oldest_first: Optional[bool] = None,
|
||||||
|
) -> HistoryIterator:
|
||||||
"""Returns an :class:`~discord.AsyncIterator` that enables receiving the destination's message history.
|
"""Returns an :class:`~discord.AsyncIterator` that enables receiving the destination's message history.
|
||||||
|
|
||||||
You must have :attr:`~discord.Permissions.read_message_history` permissions to use this.
|
You must have :attr:`~discord.Permissions.read_message_history` permissions to use this.
|
||||||
@@ -1489,19 +1613,28 @@ class Connectable(Protocol):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
_state: ConnectionState
|
||||||
|
|
||||||
def _get_voice_client_key(self):
|
def _get_voice_client_key(self) -> Tuple[int, str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _get_voice_state_pair(self):
|
def _get_voice_state_pair(self) -> Tuple[int, int]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def connect(self, *, timeout: float = 60.0, reconnect: bool = True, cls: Type[T] = VoiceClient) -> T:
|
async def connect(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
timeout: float = 60.0,
|
||||||
|
reconnect: bool = True,
|
||||||
|
cls: Callable[[Client, Connectable], T] = VoiceClient,
|
||||||
|
) -> T:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
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`
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import List, TYPE_CHECKING
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union, overload
|
||||||
|
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
from .enums import ActivityType, try_enum
|
from .enums import ActivityType, try_enum
|
||||||
@@ -92,6 +92,7 @@ t.ActivityFlags = {
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .types.activity import (
|
from .types.activity import (
|
||||||
|
Activity as ActivityPayload,
|
||||||
ActivityTimestamps,
|
ActivityTimestamps,
|
||||||
ActivityParty,
|
ActivityParty,
|
||||||
ActivityAssets,
|
ActivityAssets,
|
||||||
@@ -121,16 +122,19 @@ class BaseActivity:
|
|||||||
__slots__ = ('_created_at',)
|
__slots__ = ('_created_at',)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self._created_at = kwargs.pop('created_at', None)
|
self._created_at: Optional[float] = kwargs.pop('created_at', None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created_at(self):
|
def created_at(self) -> Optional[datetime.datetime]:
|
||||||
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC.
|
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
"""
|
"""
|
||||||
if self._created_at is not None:
|
if self._created_at is not None:
|
||||||
return datetime.datetime.utcfromtimestamp(self._created_at / 1000)
|
return datetime.datetime.fromtimestamp(self._created_at / 1000, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
|
def to_dict(self) -> ActivityPayload:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Activity(BaseActivity):
|
class Activity(BaseActivity):
|
||||||
@@ -147,17 +151,17 @@ class Activity(BaseActivity):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
------------
|
------------
|
||||||
application_id: :class:`int`
|
application_id: Optional[:class:`int`]
|
||||||
The application ID of the game.
|
The application ID of the game.
|
||||||
name: :class:`str`
|
name: Optional[:class:`str`]
|
||||||
The name of the activity.
|
The name of the activity.
|
||||||
url: :class:`str`
|
url: Optional[:class:`str`]
|
||||||
A stream URL that the activity could be doing.
|
A stream URL that the activity could be doing.
|
||||||
type: :class:`ActivityType`
|
type: :class:`ActivityType`
|
||||||
The type of activity currently being done.
|
The type of activity currently being done.
|
||||||
state: :class:`str`
|
state: Optional[:class:`str`]
|
||||||
The user's current state. For example, "In Game".
|
The user's current state. For example, "In Game".
|
||||||
details: :class:`str`
|
details: Optional[:class:`str`]
|
||||||
The detail of the user's current activity.
|
The detail of the user's current activity.
|
||||||
timestamps: :class:`dict`
|
timestamps: :class:`dict`
|
||||||
A dictionary of timestamps. It contains the following optional keys:
|
A dictionary of timestamps. It contains the following optional keys:
|
||||||
@@ -214,29 +218,28 @@ class Activity(BaseActivity):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.state = kwargs.pop('state', None)
|
self.state: Optional[str] = kwargs.pop('state', None)
|
||||||
self.details = kwargs.pop('details', None)
|
self.details: Optional[str] = kwargs.pop('details', None)
|
||||||
self.timestamps: ActivityTimestamps = kwargs.pop('timestamps', {})
|
self.timestamps: ActivityTimestamps = kwargs.pop('timestamps', {})
|
||||||
self.assets: ActivityAssets = kwargs.pop('assets', {})
|
self.assets: ActivityAssets = kwargs.pop('assets', {})
|
||||||
self.party: ActivityParty = kwargs.pop('party', {})
|
self.party: ActivityParty = kwargs.pop('party', {})
|
||||||
self.application_id = _get_as_snowflake(kwargs, 'application_id')
|
self.application_id: Optional[int] = _get_as_snowflake(kwargs, 'application_id')
|
||||||
self.name = kwargs.pop('name', None)
|
self.name: Optional[str] = kwargs.pop('name', None)
|
||||||
self.url = kwargs.pop('url', None)
|
self.url: Optional[str] = kwargs.pop('url', None)
|
||||||
self.flags = kwargs.pop('flags', 0)
|
self.flags: int = kwargs.pop('flags', 0)
|
||||||
self.sync_id = kwargs.pop('sync_id', None)
|
self.sync_id: Optional[str] = kwargs.pop('sync_id', None)
|
||||||
self.session_id = kwargs.pop('session_id', None)
|
self.session_id: Optional[str] = kwargs.pop('session_id', None)
|
||||||
self.buttons: List[ActivityButton] = kwargs.pop('buttons', [])
|
self.buttons: List[ActivityButton] = kwargs.pop('buttons', [])
|
||||||
|
|
||||||
activity_type = kwargs.pop('type', -1)
|
activity_type = kwargs.pop('type', -1)
|
||||||
self.type = activity_type if isinstance(activity_type, ActivityType) else try_enum(ActivityType, activity_type)
|
self.type: ActivityType = (
|
||||||
|
activity_type if isinstance(activity_type, ActivityType) else try_enum(ActivityType, activity_type)
|
||||||
|
)
|
||||||
|
|
||||||
emoji = kwargs.pop('emoji', None)
|
emoji = kwargs.pop('emoji', None)
|
||||||
if emoji is not None:
|
self.emoji: Optional[PartialEmoji] = PartialEmoji.from_dict(emoji) if emoji is not None else None
|
||||||
self.emoji = PartialEmoji.from_dict(emoji)
|
|
||||||
else:
|
|
||||||
self.emoji = None
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
attrs = (
|
attrs = (
|
||||||
('type', self.type),
|
('type', self.type),
|
||||||
('name', self.name),
|
('name', self.name),
|
||||||
@@ -249,8 +252,8 @@ class Activity(BaseActivity):
|
|||||||
inner = ' '.join('%s=%r' % t for t in attrs)
|
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||||
return f'<Activity {inner}>'
|
return f'<Activity {inner}>'
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
ret = {}
|
ret: Dict[str, Any] = {}
|
||||||
for attr in self.__slots__:
|
for attr in self.__slots__:
|
||||||
value = getattr(self, attr, None)
|
value = getattr(self, attr, None)
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -266,27 +269,27 @@ class Activity(BaseActivity):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def start(self):
|
def start(self) -> Optional[datetime.datetime]:
|
||||||
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
|
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
|
||||||
try:
|
try:
|
||||||
timestamp = self.timestamps['start'] / 1000
|
timestamp = self.timestamps['start'] / 1000
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def end(self):
|
def end(self) -> Optional[datetime.datetime]:
|
||||||
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
|
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
|
||||||
try:
|
try:
|
||||||
timestamp = self.timestamps['end'] / 1000
|
timestamp = self.timestamps['end'] / 1000
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def large_image_url(self):
|
def large_image_url(self) -> Optional[str]:
|
||||||
"""Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity if applicable."""
|
"""Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity if applicable."""
|
||||||
if self.application_id is None:
|
if self.application_id is None:
|
||||||
return None
|
return None
|
||||||
@@ -299,7 +302,7 @@ class Activity(BaseActivity):
|
|||||||
return Asset.BASE + f'/app-assets/{self.application_id}/{large_image}.png'
|
return Asset.BASE + f'/app-assets/{self.application_id}/{large_image}.png'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def small_image_url(self):
|
def small_image_url(self) -> Optional[str]:
|
||||||
"""Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity if applicable."""
|
"""Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity if applicable."""
|
||||||
if self.application_id is None:
|
if self.application_id is None:
|
||||||
return None
|
return None
|
||||||
@@ -312,12 +315,12 @@ class Activity(BaseActivity):
|
|||||||
return Asset.BASE + f'/app-assets/{self.application_id}/{small_image}.png'
|
return Asset.BASE + f'/app-assets/{self.application_id}/{small_image}.png'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def large_image_text(self):
|
def large_image_text(self) -> Optional[str]:
|
||||||
"""Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable."""
|
"""Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable."""
|
||||||
return self.assets.get('large_text', None)
|
return self.assets.get('large_text', None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def small_image_text(self):
|
def small_image_text(self) -> Optional[str]:
|
||||||
"""Optional[:class:`str`]: Returns the small image asset hover text of this activity if applicable."""
|
"""Optional[:class:`str`]: Returns the small image asset hover text of this activity if applicable."""
|
||||||
return self.assets.get('small_text', None)
|
return self.assets.get('small_text', None)
|
||||||
|
|
||||||
@@ -358,9 +361,9 @@ class Game(BaseActivity):
|
|||||||
|
|
||||||
__slots__ = ('name', '_end', '_start')
|
__slots__ = ('name', '_end', '_start')
|
||||||
|
|
||||||
def __init__(self, name, **extra):
|
def __init__(self, name: str, **extra):
|
||||||
super().__init__(**extra)
|
super().__init__(**extra)
|
||||||
self.name = name
|
self.name: str = name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestamps: ActivityTimestamps = extra['timestamps']
|
timestamps: ActivityTimestamps = extra['timestamps']
|
||||||
@@ -372,7 +375,7 @@ class Game(BaseActivity):
|
|||||||
self._end = timestamps.get('end', 0)
|
self._end = timestamps.get('end', 0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self) -> ActivityType:
|
||||||
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
||||||
|
|
||||||
It always returns :attr:`ActivityType.playing`.
|
It always returns :attr:`ActivityType.playing`.
|
||||||
@@ -380,27 +383,27 @@ class Game(BaseActivity):
|
|||||||
return ActivityType.playing
|
return ActivityType.playing
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def start(self):
|
def start(self) -> Optional[datetime.datetime]:
|
||||||
"""Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable."""
|
"""Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable."""
|
||||||
if self._start:
|
if self._start:
|
||||||
return datetime.datetime.utcfromtimestamp(self._start / 1000).replace(tzinfo=datetime.timezone.utc)
|
return datetime.datetime.fromtimestamp(self._start / 1000, tz=datetime.timezone.utc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def end(self):
|
def end(self) -> Optional[datetime.datetime]:
|
||||||
"""Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable."""
|
"""Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable."""
|
||||||
if self._end:
|
if self._end:
|
||||||
return datetime.datetime.utcfromtimestamp(self._end / 1000).replace(tzinfo=datetime.timezone.utc)
|
return datetime.datetime.fromtimestamp(self._end / 1000, tz=datetime.timezone.utc)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<Game name={self.name!r}>'
|
return f'<Game name={self.name!r}>'
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
timestamps = {}
|
timestamps: Dict[str, Any] = {}
|
||||||
if self._start:
|
if self._start:
|
||||||
timestamps['start'] = self._start
|
timestamps['start'] = self._start
|
||||||
|
|
||||||
@@ -415,13 +418,13 @@ class Game(BaseActivity):
|
|||||||
}
|
}
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return isinstance(other, Game) and other.name == self.name
|
return isinstance(other, Game) and other.name == self.name
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash(self.name)
|
return hash(self.name)
|
||||||
|
|
||||||
|
|
||||||
@@ -450,7 +453,7 @@ class Streaming(BaseActivity):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
platform: :class:`str`
|
platform: Optional[:class:`str`]
|
||||||
Where the user is streaming from (ie. YouTube, Twitch).
|
Where the user is streaming from (ie. YouTube, Twitch).
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
@@ -472,27 +475,27 @@ class Streaming(BaseActivity):
|
|||||||
|
|
||||||
__slots__ = ('platform', 'name', 'game', 'url', 'details', 'assets')
|
__slots__ = ('platform', 'name', 'game', 'url', 'details', 'assets')
|
||||||
|
|
||||||
def __init__(self, *, name, url, **extra):
|
def __init__(self, *, name: Optional[str], url: str, **extra: Any):
|
||||||
super().__init__(**extra)
|
super().__init__(**extra)
|
||||||
self.platform = name
|
self.platform: Optional[str] = name
|
||||||
self.name = extra.pop('details', name)
|
self.name: Optional[str] = extra.pop('details', name)
|
||||||
self.game = extra.pop('state', None)
|
self.game: Optional[str] = extra.pop('state', None)
|
||||||
self.url = url
|
self.url: str = url
|
||||||
self.details = extra.pop('details', self.name) # compatibility
|
self.details: Optional[str] = extra.pop('details', self.name) # compatibility
|
||||||
self.assets: ActivityAssets = extra.pop('assets', {})
|
self.assets: ActivityAssets = extra.pop('assets', {})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self) -> ActivityType:
|
||||||
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
||||||
|
|
||||||
It always returns :attr:`ActivityType.streaming`.
|
It always returns :attr:`ActivityType.streaming`.
|
||||||
"""
|
"""
|
||||||
return ActivityType.streaming
|
return ActivityType.streaming
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<Streaming name={self.name!r}>'
|
return f'<Streaming name={self.name!r}>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -510,9 +513,9 @@ class Streaming(BaseActivity):
|
|||||||
else:
|
else:
|
||||||
return name[7:] if name[:7] == 'twitch:' else None
|
return name[7:] if name[:7] == 'twitch:' else None
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
# fmt: off
|
# fmt: off
|
||||||
ret = {
|
ret: Dict[str, Any] = {
|
||||||
'type': ActivityType.streaming.value,
|
'type': ActivityType.streaming.value,
|
||||||
'name': str(self.name),
|
'name': str(self.name),
|
||||||
'url': str(self.url),
|
'url': str(self.url),
|
||||||
@@ -523,13 +526,13 @@ class Streaming(BaseActivity):
|
|||||||
ret['details'] = self.details
|
ret['details'] = self.details
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return isinstance(other, Streaming) and other.name == self.name and other.url == self.url
|
return isinstance(other, Streaming) and other.name == self.name and other.url == self.url
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash(self.name)
|
return hash(self.name)
|
||||||
|
|
||||||
|
|
||||||
@@ -559,17 +562,17 @@ class Spotify:
|
|||||||
__slots__ = ('_state', '_details', '_timestamps', '_assets', '_party', '_sync_id', '_session_id', '_created_at')
|
__slots__ = ('_state', '_details', '_timestamps', '_assets', '_party', '_sync_id', '_session_id', '_created_at')
|
||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
self._state = data.pop('state', None)
|
self._state: str = data.pop('state', '')
|
||||||
self._details = data.pop('details', None)
|
self._details: str = data.pop('details', '')
|
||||||
self._timestamps = data.pop('timestamps', {})
|
self._timestamps: Dict[str, int] = data.pop('timestamps', {})
|
||||||
self._assets = data.pop('assets', {})
|
self._assets: ActivityAssets = data.pop('assets', {})
|
||||||
self._party = data.pop('party', {})
|
self._party: ActivityParty = data.pop('party', {})
|
||||||
self._sync_id = data.pop('sync_id')
|
self._sync_id: str = data.pop('sync_id')
|
||||||
self._session_id = data.pop('session_id')
|
self._session_id: str = data.pop('session_id')
|
||||||
self._created_at = data.pop('created_at', None)
|
self._created_at: Optional[float] = data.pop('created_at', None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self) -> ActivityType:
|
||||||
""":class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`.
|
""":class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`.
|
||||||
|
|
||||||
It always returns :attr:`ActivityType.listening`.
|
It always returns :attr:`ActivityType.listening`.
|
||||||
@@ -577,29 +580,29 @@ class Spotify:
|
|||||||
return ActivityType.listening
|
return ActivityType.listening
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created_at(self):
|
def created_at(self) -> Optional[datetime.datetime]:
|
||||||
"""Optional[:class:`datetime.datetime`]: When the user started listening in UTC.
|
"""Optional[:class:`datetime.datetime`]: When the user started listening in UTC.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
"""
|
"""
|
||||||
if self._created_at is not None:
|
if self._created_at is not None:
|
||||||
return datetime.datetime.utcfromtimestamp(self._created_at / 1000)
|
return datetime.datetime.fromtimestamp(self._created_at / 1000, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def colour(self):
|
def colour(self) -> Colour:
|
||||||
""":class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`.
|
""":class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`.
|
||||||
|
|
||||||
There is an alias for this named :attr:`color`"""
|
There is an alias for this named :attr:`color`"""
|
||||||
return Colour(0x1DB954)
|
return Colour(0x1DB954)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color(self):
|
def color(self) -> Colour:
|
||||||
""":class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`.
|
""":class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`.
|
||||||
|
|
||||||
There is an alias for this named :attr:`colour`"""
|
There is an alias for this named :attr:`colour`"""
|
||||||
return self.colour
|
return self.colour
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'flags': 48, # SYNC | PLAY
|
'flags': 48, # SYNC | PLAY
|
||||||
'name': 'Spotify',
|
'name': 'Spotify',
|
||||||
@@ -613,11 +616,11 @@ class Spotify:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
""":class:`str`: The activity's name. This will always return "Spotify"."""
|
""":class:`str`: The activity's name. This will always return "Spotify"."""
|
||||||
return 'Spotify'
|
return 'Spotify'
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return (
|
return (
|
||||||
isinstance(other, Spotify)
|
isinstance(other, Spotify)
|
||||||
and other._session_id == self._session_id
|
and other._session_id == self._session_id
|
||||||
@@ -625,30 +628,30 @@ class Spotify:
|
|||||||
and other.start == self.start
|
and other.start == self.start
|
||||||
)
|
)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash(self._session_id)
|
return hash(self._session_id)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return 'Spotify'
|
return 'Spotify'
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<Spotify title={self.title!r} artist={self.artist!r} track_id={self.track_id!r}>'
|
return f'<Spotify title={self.title!r} artist={self.artist!r} track_id={self.track_id!r}>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self) -> str:
|
||||||
""":class:`str`: The title of the song being played."""
|
""":class:`str`: The title of the song being played."""
|
||||||
return self._details
|
return self._details
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def artists(self):
|
def artists(self) -> List[str]:
|
||||||
"""List[:class:`str`]: The artists of the song being played."""
|
"""List[:class:`str`]: The artists of the song being played."""
|
||||||
return self._state.split('; ')
|
return self._state.split('; ')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def artist(self):
|
def artist(self) -> str:
|
||||||
""":class:`str`: The artist of the song being played.
|
""":class:`str`: The artist of the song being played.
|
||||||
|
|
||||||
This does not attempt to split the artist information into
|
This does not attempt to split the artist information into
|
||||||
@@ -657,12 +660,12 @@ class Spotify:
|
|||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def album(self):
|
def album(self) -> str:
|
||||||
""":class:`str`: The album that the song being played belongs to."""
|
""":class:`str`: The album that the song being played belongs to."""
|
||||||
return self._assets.get('large_text', '')
|
return self._assets.get('large_text', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def album_cover_url(self):
|
def album_cover_url(self) -> str:
|
||||||
""":class:`str`: The album cover image URL from Spotify's CDN."""
|
""":class:`str`: The album cover image URL from Spotify's CDN."""
|
||||||
large_image = self._assets.get('large_image', '')
|
large_image = self._assets.get('large_image', '')
|
||||||
if large_image[:8] != 'spotify:':
|
if large_image[:8] != 'spotify:':
|
||||||
@@ -671,27 +674,35 @@ class Spotify:
|
|||||||
return 'https://i.scdn.co/image/' + album_image_id
|
return 'https://i.scdn.co/image/' + album_image_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def track_id(self):
|
def track_id(self) -> str:
|
||||||
""":class:`str`: The track ID used by Spotify to identify this song."""
|
""":class:`str`: The track ID used by Spotify to identify this song."""
|
||||||
return self._sync_id
|
return self._sync_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def start(self):
|
def track_url(self) -> str:
|
||||||
|
""":class:`str`: The track URL to listen on Spotify.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
return f'https://open.spotify.com/track/{self.track_id}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def start(self) -> datetime.datetime:
|
||||||
""":class:`datetime.datetime`: When the user started playing this song in UTC."""
|
""":class:`datetime.datetime`: When the user started playing this song in UTC."""
|
||||||
return datetime.datetime.utcfromtimestamp(self._timestamps['start'] / 1000)
|
return datetime.datetime.fromtimestamp(self._timestamps['start'] / 1000, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def end(self):
|
def end(self) -> datetime.datetime:
|
||||||
""":class:`datetime.datetime`: When the user will stop playing this song in UTC."""
|
""":class:`datetime.datetime`: When the user will stop playing this song in UTC."""
|
||||||
return datetime.datetime.utcfromtimestamp(self._timestamps['end'] / 1000)
|
return datetime.datetime.fromtimestamp(self._timestamps['end'] / 1000, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self) -> datetime.timedelta:
|
||||||
""":class:`datetime.timedelta`: The duration of the song being played."""
|
""":class:`datetime.timedelta`: The duration of the song being played."""
|
||||||
return self.end - self.start
|
return self.end - self.start
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def party_id(self):
|
def party_id(self) -> str:
|
||||||
""":class:`str`: The party ID of the listening party."""
|
""":class:`str`: The party ID of the listening party."""
|
||||||
return self._party.get('id', '')
|
return self._party.get('id', '')
|
||||||
|
|
||||||
@@ -729,13 +740,14 @@ class CustomActivity(BaseActivity):
|
|||||||
|
|
||||||
__slots__ = ('name', 'emoji', 'state')
|
__slots__ = ('name', 'emoji', 'state')
|
||||||
|
|
||||||
def __init__(self, name, *, emoji=None, **extra):
|
def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, **extra: Any):
|
||||||
super().__init__(**extra)
|
super().__init__(**extra)
|
||||||
self.name = name
|
self.name: Optional[str] = name
|
||||||
self.state = extra.pop('state', None)
|
self.state: Optional[str] = extra.pop('state', None)
|
||||||
if self.name == 'Custom Status':
|
if self.name == 'Custom Status':
|
||||||
self.name = self.state
|
self.name = self.state
|
||||||
|
|
||||||
|
self.emoji: Optional[PartialEmoji]
|
||||||
if emoji is None:
|
if emoji is None:
|
||||||
self.emoji = emoji
|
self.emoji = emoji
|
||||||
elif isinstance(emoji, dict):
|
elif isinstance(emoji, dict):
|
||||||
@@ -748,14 +760,14 @@ class CustomActivity(BaseActivity):
|
|||||||
raise TypeError(f'Expected str, PartialEmoji, or None, received {type(emoji)!r} instead.')
|
raise TypeError(f'Expected str, PartialEmoji, or None, received {type(emoji)!r} instead.')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self) -> ActivityType:
|
||||||
""":class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`.
|
""":class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`.
|
||||||
|
|
||||||
It always returns :attr:`ActivityType.custom`.
|
It always returns :attr:`ActivityType.custom`.
|
||||||
"""
|
"""
|
||||||
return ActivityType.custom
|
return ActivityType.custom
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
if self.name == self.state:
|
if self.name == self.state:
|
||||||
o = {
|
o = {
|
||||||
'type': ActivityType.custom.value,
|
'type': ActivityType.custom.value,
|
||||||
@@ -772,16 +784,16 @@ class CustomActivity(BaseActivity):
|
|||||||
o['emoji'] = self.emoji.to_dict()
|
o['emoji'] = self.emoji.to_dict()
|
||||||
return o
|
return o
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji
|
return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash((self.name, str(self.emoji)))
|
return hash((self.name, str(self.emoji)))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
if self.emoji:
|
if self.emoji:
|
||||||
if self.name:
|
if self.name:
|
||||||
return f'{self.emoji} {self.name}'
|
return f'{self.emoji} {self.name}'
|
||||||
@@ -789,11 +801,21 @@ class CustomActivity(BaseActivity):
|
|||||||
else:
|
else:
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<CustomActivity name={self.name!r} emoji={self.emoji!r}>'
|
return f'<CustomActivity name={self.name!r} emoji={self.emoji!r}>'
|
||||||
|
|
||||||
|
|
||||||
def create_activity(data):
|
ActivityTypes = Union[Activity, Game, CustomActivity, Streaming, Spotify]
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def create_activity(data: ActivityPayload) -> ActivityTypes:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def create_activity(data: None) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def create_activity(data: Optional[ActivityPayload]) -> Optional[ActivityTypes]:
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -808,10 +830,12 @@ def create_activity(data):
|
|||||||
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)
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class AppInfo:
|
|||||||
self.rpc_origins: List[str] = data['rpc_origins']
|
self.rpc_origins: List[str] = data['rpc_origins']
|
||||||
self.bot_public: bool = data['bot_public']
|
self.bot_public: bool = data['bot_public']
|
||||||
self.bot_require_code_grant: bool = data['bot_require_code_grant']
|
self.bot_require_code_grant: bool = data['bot_require_code_grant']
|
||||||
self.owner: User = state.store_user(data['owner'])
|
self.owner: User = state.create_user(data['owner'])
|
||||||
|
|
||||||
team: Optional[TeamPayload] = data.get('team')
|
team: Optional[TeamPayload] = data.get('team')
|
||||||
self.team: Optional[Team] = Team(state, team) if team else None
|
self.team: Optional[Team] = Team(state, team) if team else None
|
||||||
@@ -196,7 +196,7 @@ class AppInfo:
|
|||||||
return self._state._get_guild(self.guild_id)
|
return self._state._get_guild(self.guild_id)
|
||||||
|
|
||||||
class PartialAppInfo:
|
class PartialAppInfo:
|
||||||
"""Represents a partial AppInfo given by :func:`~GuildChannel.create_invite`
|
"""Represents a partial AppInfo given by :func:`~discord.abc.GuildChannel.create_invite`
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
@@ -216,14 +227,25 @@ class Asset(AssetMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_sticker(cls, state, sticker_id: int, sticker_hash: str) -> Asset:
|
def _from_sticker_banner(cls, state, banner: int) -> Asset:
|
||||||
return cls(
|
return cls(
|
||||||
state,
|
state,
|
||||||
url=f'{cls.BASE}/stickers/{sticker_id}/{sticker_hash}.png?size=1024',
|
url=f'{cls.BASE}/app-assets/710982414301790216/store/{banner}.png',
|
||||||
key=sticker_hash,
|
key=str(banner),
|
||||||
animated=False,
|
animated=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_user_banner(cls, state, user_id: int, banner_hash: str) -> Asset:
|
||||||
|
animated = banner_hash.startswith('a_')
|
||||||
|
format = 'gif' if animated else 'png'
|
||||||
|
return cls(
|
||||||
|
state,
|
||||||
|
url=f'{cls.BASE}/banners/{user_id}/{banner_hash}.{format}?size=512',
|
||||||
|
key=banner_hash,
|
||||||
|
animated=animated
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self._url
|
return self._url
|
||||||
|
|
||||||
@@ -256,6 +278,7 @@ class Asset(AssetMixin):
|
|||||||
|
|
||||||
def replace(
|
def replace(
|
||||||
self,
|
self,
|
||||||
|
*,
|
||||||
size: int = MISSING,
|
size: int = MISSING,
|
||||||
format: ValidAssetFormatTypes = MISSING,
|
format: ValidAssetFormatTypes = MISSING,
|
||||||
static_format: ValidStaticFormatTypes = MISSING,
|
static_format: ValidStaticFormatTypes = MISSING,
|
||||||
@@ -290,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}')
|
||||||
@@ -310,7 +334,7 @@ class Asset(AssetMixin):
|
|||||||
url = str(url)
|
url = str(url)
|
||||||
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
||||||
|
|
||||||
def with_size(self, size: int) -> Asset:
|
def with_size(self, size: int, /) -> Asset:
|
||||||
"""Returns a new asset with the specified size.
|
"""Returns a new asset with the specified size.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -334,7 +358,7 @@ class Asset(AssetMixin):
|
|||||||
url = str(yarl.URL(self._url).with_query(size=size))
|
url = str(yarl.URL(self._url).with_query(size=size))
|
||||||
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
||||||
|
|
||||||
def with_format(self, format: ValidAssetFormatTypes) -> Asset:
|
def with_format(self, format: ValidAssetFormatTypes, /) -> Asset:
|
||||||
"""Returns a new asset with the specified format.
|
"""Returns a new asset with the specified format.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -365,7 +389,7 @@ class Asset(AssetMixin):
|
|||||||
url = str(url.with_path(f'{path}.{format}').with_query(url.raw_query_string))
|
url = str(url.with_path(f'{path}.{format}').with_query(url.raw_query_string))
|
||||||
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
|
||||||
|
|
||||||
def with_static_format(self, format: ValidStaticFormatTypes) -> Asset:
|
def with_static_format(self, format: ValidStaticFormatTypes, /) -> Asset:
|
||||||
"""Returns a new asset with the specified static format.
|
"""Returns a new asset with the specified static format.
|
||||||
|
|
||||||
This only changes the format if the underlying asset is
|
This only changes the format if the underlying asset is
|
||||||
|
|||||||
@@ -49,12 +49,17 @@ if TYPE_CHECKING:
|
|||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .member import Member
|
from .member import Member
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .types.audit_log import AuditLogChange as AuditLogChangePayload
|
from .types.audit_log import (
|
||||||
from .types.audit_log import AuditLogEntry as AuditLogEntryPayload
|
AuditLogChange as AuditLogChangePayload,
|
||||||
|
AuditLogEntry as AuditLogEntryPayload,
|
||||||
|
)
|
||||||
from .types.channel import PermissionOverwrite as PermissionOverwritePayload
|
from .types.channel import PermissionOverwrite as PermissionOverwritePayload
|
||||||
from .types.role import Role as RolePayload
|
from .types.role import Role as RolePayload
|
||||||
from .types.snowflake import Snowflake
|
from .types.snowflake import Snowflake
|
||||||
from .user import User
|
from .user import User
|
||||||
|
from .stage_instance import StageInstance
|
||||||
|
from .sticker import GuildSticker
|
||||||
|
from .threads import Thread
|
||||||
|
|
||||||
|
|
||||||
def _transform_permissions(entry: AuditLogEntry, data: str) -> Permissions:
|
def _transform_permissions(entry: AuditLogEntry, data: str) -> Permissions:
|
||||||
@@ -69,22 +74,21 @@ def _transform_snowflake(entry: AuditLogEntry, data: Snowflake) -> int:
|
|||||||
return int(data)
|
return int(data)
|
||||||
|
|
||||||
|
|
||||||
def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Object]:
|
def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Union[abc.GuildChannel, Object]]:
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
return entry.guild.get_channel(int(data)) or Object(id=data)
|
return entry.guild.get_channel(int(data)) or Object(id=data)
|
||||||
|
|
||||||
|
|
||||||
def _transform_owner_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Union[Member, User, None]:
|
def _transform_member_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Union[Member, User, None]:
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
return entry._get_member(int(data))
|
return entry._get_member(int(data))
|
||||||
|
|
||||||
|
def _transform_guild_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Guild]:
|
||||||
def _transform_inviter_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Union[Member, User, None]:
|
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
return entry._get_member(int(data))
|
return entry._state._get_guild(data)
|
||||||
|
|
||||||
|
|
||||||
def _transform_overwrites(
|
def _transform_overwrites(
|
||||||
@@ -92,8 +96,8 @@ def _transform_overwrites(
|
|||||||
) -> List[Tuple[Object, PermissionOverwrite]]:
|
) -> List[Tuple[Object, PermissionOverwrite]]:
|
||||||
overwrites = []
|
overwrites = []
|
||||||
for elem in data:
|
for elem in data:
|
||||||
allow = Permissions(elem['allow'])
|
allow = Permissions(int(elem['allow']))
|
||||||
deny = Permissions(elem['deny'])
|
deny = Permissions(int(elem['deny']))
|
||||||
ow = PermissionOverwrite.from_pair(allow, deny)
|
ow = PermissionOverwrite.from_pair(allow, deny)
|
||||||
|
|
||||||
ow_type = elem['type']
|
ow_type = elem['type']
|
||||||
@@ -142,6 +146,11 @@ def _enum_transformer(enum: Type[T]) -> Callable[[AuditLogEntry, int], T]:
|
|||||||
|
|
||||||
return _transform
|
return _transform
|
||||||
|
|
||||||
|
def _transform_type(entry: AuditLogEntry, data: Union[int]) -> Union[enums.ChannelType, enums.StickerType]:
|
||||||
|
if entry.action.name.startswith('sticker_'):
|
||||||
|
return enums.try_enum(enums.StickerType, data)
|
||||||
|
else:
|
||||||
|
return enums.try_enum(enums.ChannelType, data)
|
||||||
|
|
||||||
class AuditLogDiff:
|
class AuditLogDiff:
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
@@ -176,8 +185,8 @@ class AuditLogChanges:
|
|||||||
'permissions': (None, _transform_permissions),
|
'permissions': (None, _transform_permissions),
|
||||||
'id': (None, _transform_snowflake),
|
'id': (None, _transform_snowflake),
|
||||||
'color': ('colour', _transform_color),
|
'color': ('colour', _transform_color),
|
||||||
'owner_id': ('owner', _transform_owner_id),
|
'owner_id': ('owner', _transform_member_id),
|
||||||
'inviter_id': ('inviter', _transform_inviter_id),
|
'inviter_id': ('inviter', _transform_member_id),
|
||||||
'channel_id': ('channel', _transform_channel),
|
'channel_id': ('channel', _transform_channel),
|
||||||
'afk_channel_id': ('afk_channel', _transform_channel),
|
'afk_channel_id': ('afk_channel', _transform_channel),
|
||||||
'system_channel_id': ('system_channel', _transform_channel),
|
'system_channel_id': ('system_channel', _transform_channel),
|
||||||
@@ -191,12 +200,15 @@ class AuditLogChanges:
|
|||||||
'icon_hash': ('icon', _transform_icon),
|
'icon_hash': ('icon', _transform_icon),
|
||||||
'avatar_hash': ('avatar', _transform_avatar),
|
'avatar_hash': ('avatar', _transform_avatar),
|
||||||
'rate_limit_per_user': ('slowmode_delay', None),
|
'rate_limit_per_user': ('slowmode_delay', None),
|
||||||
|
'guild_id': ('guild', _transform_guild_id),
|
||||||
|
'tags': ('emoji', None),
|
||||||
'default_message_notifications': ('default_notifications', _enum_transformer(enums.NotificationLevel)),
|
'default_message_notifications': ('default_notifications', _enum_transformer(enums.NotificationLevel)),
|
||||||
'region': (None, _enum_transformer(enums.VoiceRegion)),
|
'region': (None, _enum_transformer(enums.VoiceRegion)),
|
||||||
'rtc_region': (None, _enum_transformer(enums.VoiceRegion)),
|
'rtc_region': (None, _enum_transformer(enums.VoiceRegion)),
|
||||||
'video_quality_mode': (None, _enum_transformer(enums.VideoQualityMode)),
|
'video_quality_mode': (None, _enum_transformer(enums.VideoQualityMode)),
|
||||||
'privacy_level': (None, _enum_transformer(enums.StagePrivacyLevel)),
|
'privacy_level': (None, _enum_transformer(enums.StagePrivacyLevel)),
|
||||||
'type': (None, _enum_transformer(enums.ChannelType)),
|
'format_type': (None, _enum_transformer(enums.StickerFormatType)),
|
||||||
|
'type': (None, _transform_type),
|
||||||
}
|
}
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@@ -318,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.
|
||||||
|
|
||||||
@@ -434,7 +450,7 @@ class AuditLogEntry(Hashable):
|
|||||||
return utils.snowflake_time(self.id)
|
return utils.snowflake_time(self.id)
|
||||||
|
|
||||||
@utils.cached_property
|
@utils.cached_property
|
||||||
def target(self) -> Union[Guild, abc.GuildChannel, Member, User, Role, Invite, Emoji, Object, None]:
|
def target(self) -> Union[Guild, abc.GuildChannel, Member, User, Role, Invite, Emoji, StageInstance, GuildSticker, Thread, Object, None]:
|
||||||
try:
|
try:
|
||||||
converter = getattr(self, '_convert_target_' + self.action.target_type)
|
converter = getattr(self, '_convert_target_' + self.action.target_type)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -501,3 +517,12 @@ class AuditLogEntry(Hashable):
|
|||||||
|
|
||||||
def _convert_target_message(self, target_id: int) -> Union[Member, User, None]:
|
def _convert_target_message(self, target_id: int) -> Union[Member, User, None]:
|
||||||
return self._get_member(target_id)
|
return self._get_member(target_id)
|
||||||
|
|
||||||
|
def _convert_target_stage_instance(self, target_id: int) -> Union[StageInstance, Object]:
|
||||||
|
return self.guild.get_stage_instance(target_id) or Object(id=target_id)
|
||||||
|
|
||||||
|
def _convert_target_sticker(self, target_id: int) -> Union[GuildSticker, Object]:
|
||||||
|
return self._state.get_sticker(target_id) or Object(id=target_id)
|
||||||
|
|
||||||
|
def _convert_target_thread(self, target_id: int) -> Union[Thread, Object]:
|
||||||
|
return self.guild.get_thread(target_id) or Object(id=target_id)
|
||||||
|
|||||||
@@ -22,14 +22,20 @@ 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 time
|
import time
|
||||||
import random
|
import random
|
||||||
|
from typing import Callable, Generic, Literal, TypeVar, overload, Union
|
||||||
|
|
||||||
|
T = TypeVar('T', bool, Literal[True], Literal[False])
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ExponentialBackoff',
|
'ExponentialBackoff',
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExponentialBackoff:
|
class ExponentialBackoff(Generic[T]):
|
||||||
"""An implementation of the exponential backoff algorithm
|
"""An implementation of the exponential backoff algorithm
|
||||||
|
|
||||||
Provides a convenient interface to implement an exponential backoff
|
Provides a convenient interface to implement an exponential backoff
|
||||||
@@ -51,21 +57,33 @@ class ExponentialBackoff:
|
|||||||
number in between may be returned.
|
number in between may be returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, base=1, *, integral=False):
|
def __init__(self, base: int = 1, *, integral: T = False):
|
||||||
self._base = base
|
self._base: int = base
|
||||||
|
|
||||||
self._exp = 0
|
self._exp: int = 0
|
||||||
self._max = 10
|
self._max: int = 10
|
||||||
self._reset_time = base * 2 ** 11
|
self._reset_time: int = base * 2 ** 11
|
||||||
self._last_invocation = time.monotonic()
|
self._last_invocation: float = time.monotonic()
|
||||||
|
|
||||||
# Use our own random instance to avoid messing with global one
|
# Use our own random instance to avoid messing with global one
|
||||||
rand = random.Random()
|
rand = random.Random()
|
||||||
rand.seed()
|
rand.seed()
|
||||||
|
|
||||||
self._randfunc = rand.randrange if integral else rand.uniform
|
self._randfunc: Callable[..., Union[int, float]] = rand.randrange if integral else rand.uniform # type: ignore
|
||||||
|
|
||||||
def delay(self):
|
@overload
|
||||||
|
def delay(self: ExponentialBackoff[Literal[False]]) -> float:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def delay(self: ExponentialBackoff[Literal[True]]) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def delay(self: ExponentialBackoff[bool]) -> Union[int, float]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def delay(self) -> Union[int, float]:
|
||||||
"""Compute the next delay
|
"""Compute the next delay
|
||||||
|
|
||||||
Returns the next delay to wait according to the exponential
|
Returns the next delay to wait according to the exponential
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -29,27 +29,29 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Generator, List, Optional, Sequence, TYPE_CHECKING, TypeVar, Union
|
from typing import Any, Callable, Coroutine, Dict, Generator, List, Optional, Sequence, TYPE_CHECKING, Tuple, TypeVar, Union
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .user import User
|
from .user import User, ClientUser
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
from .template import Template
|
from .template import Template
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .channel import _channel_factory
|
from .emoji import Emoji
|
||||||
|
from .channel import _threaded_channel_factory, PartialMessageable
|
||||||
from .enums import ChannelType
|
from .enums import ChannelType
|
||||||
from .mentions import AllowedMentions
|
from .mentions import AllowedMentions
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .enums import Status, VoiceRegion
|
from .enums import Status, VoiceRegion
|
||||||
from .flags import ApplicationFlags
|
from .flags import ApplicationFlags, Intents
|
||||||
from .gateway import *
|
from .gateway import *
|
||||||
from .activity import BaseActivity, create_activity
|
from .activity import ActivityTypes, BaseActivity, create_activity
|
||||||
from .voice_client import VoiceClient
|
from .voice_client import VoiceClient
|
||||||
from .http import HTTPClient
|
from .http import HTTPClient
|
||||||
from .state import ConnectionState
|
from .state import ConnectionState
|
||||||
from . import utils
|
from . import utils
|
||||||
|
from .utils import MISSING
|
||||||
from .object import Object
|
from .object import Object
|
||||||
from .backoff import ExponentialBackoff
|
from .backoff import ExponentialBackoff
|
||||||
from .webhook import Webhook
|
from .webhook import Webhook
|
||||||
@@ -57,28 +59,37 @@ from .iterators import GuildIterator
|
|||||||
from .appinfo import AppInfo
|
from .appinfo import AppInfo
|
||||||
from .ui.view import View
|
from .ui.view import View
|
||||||
from .stage_instance import StageInstance
|
from .stage_instance import StageInstance
|
||||||
|
from .threads import Thread
|
||||||
|
from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .abc import SnowflakeTime, PrivateChannel, GuildChannel, Snowflake
|
||||||
|
from .channel import DMChannel
|
||||||
|
from .message import Message
|
||||||
|
from .member import Member
|
||||||
|
from .voice_client import VoiceProtocol
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Client',
|
'Client',
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
Coro = TypeVar('Coro', bound=Callable[..., Coroutine[Any, Any, Any]])
|
||||||
from .abc import SnowflakeTime
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def _cancel_tasks(loop):
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
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()}
|
||||||
|
|
||||||
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():
|
||||||
@@ -90,12 +101,12 @@ def _cancel_tasks(loop):
|
|||||||
'task': task
|
'task': task
|
||||||
})
|
})
|
||||||
|
|
||||||
def _cleanup_loop(loop):
|
def _cleanup_loop(loop: asyncio.AbstractEventLoop) -> None:
|
||||||
try:
|
try:
|
||||||
_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:
|
||||||
@@ -116,7 +127,7 @@ class Client:
|
|||||||
The :class:`asyncio.AbstractEventLoop` to use for asynchronous operations.
|
The :class:`asyncio.AbstractEventLoop` to use for asynchronous operations.
|
||||||
Defaults to ``None``, in which case the default event loop is used via
|
Defaults to ``None``, in which case the default event loop is used via
|
||||||
:func:`asyncio.get_event_loop()`.
|
:func:`asyncio.get_event_loop()`.
|
||||||
connector: :class:`aiohttp.BaseConnector`
|
connector: Optional[:class:`aiohttp.BaseConnector`]
|
||||||
The connector to use for connection pooling.
|
The connector to use for connection pooling.
|
||||||
proxy: Optional[:class:`str`]
|
proxy: Optional[:class:`str`]
|
||||||
Proxy URL.
|
Proxy URL.
|
||||||
@@ -131,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`
|
||||||
@@ -173,6 +183,14 @@ class Client:
|
|||||||
sync your system clock to Google's NTP server.
|
sync your system clock to Google's NTP server.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
|
enable_debug_events: :class:`bool`
|
||||||
|
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
|
||||||
|
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``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
@@ -181,52 +199,62 @@ class Client:
|
|||||||
loop: :class:`asyncio.AbstractEventLoop`
|
loop: :class:`asyncio.AbstractEventLoop`
|
||||||
The event loop that the client uses for asynchronous operations.
|
The event loop that the client uses for asynchronous operations.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *, loop=None, **options):
|
def __init__(
|
||||||
self.ws = None
|
self,
|
||||||
self.loop = asyncio.get_event_loop() if loop is None else loop
|
*,
|
||||||
self._listeners = {}
|
intents: Intents,
|
||||||
self.shard_id = options.get('shard_id')
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||||
self.shard_count = options.get('shard_count')
|
**options: Any,
|
||||||
|
):
|
||||||
|
options["intents"] = intents
|
||||||
|
|
||||||
connector = options.pop('connector', None)
|
# self.ws is set in the connect method
|
||||||
proxy = options.pop('proxy', None)
|
self.ws: DiscordWebSocket = None # type: ignore
|
||||||
proxy_auth = options.pop('proxy_auth', None)
|
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop
|
||||||
unsync_clock = options.pop('assume_unsync_clock', True)
|
self._listeners: Dict[str, List[Tuple[asyncio.Future, Callable[..., bool]]]] = {}
|
||||||
self.http = HTTPClient(connector, proxy=proxy, proxy_auth=proxy_auth, unsync_clock=unsync_clock, loop=self.loop)
|
self.shard_id: Optional[int] = options.get('shard_id')
|
||||||
|
self.shard_count: Optional[int] = options.get('shard_count')
|
||||||
|
|
||||||
self._handlers = {
|
connector: Optional[aiohttp.BaseConnector] = options.pop('connector', None)
|
||||||
|
proxy: Optional[str] = options.pop('proxy', None)
|
||||||
|
proxy_auth: Optional[aiohttp.BasicAuth] = options.pop('proxy_auth', None)
|
||||||
|
unsync_clock: bool = options.pop('assume_unsync_clock', True)
|
||||||
|
self.http: HTTPClient = HTTPClient(connector, proxy=proxy, proxy_auth=proxy_auth, unsync_clock=unsync_clock, loop=self.loop)
|
||||||
|
|
||||||
|
self._handlers: Dict[str, Callable] = {
|
||||||
'ready': self._handle_ready
|
'ready': self._handle_ready
|
||||||
}
|
}
|
||||||
|
|
||||||
self._hooks = {
|
self._hooks: Dict[str, Callable] = {
|
||||||
'before_identify': self._call_before_identify_hook
|
'before_identify': self._call_before_identify_hook
|
||||||
}
|
}
|
||||||
|
|
||||||
self._connection = self._get_state(**options)
|
self._enable_debug_events: bool = options.pop('enable_debug_events', False)
|
||||||
|
self._connection: ConnectionState = self._get_state(**options)
|
||||||
self._connection.shard_count = self.shard_count
|
self._connection.shard_count = self.shard_count
|
||||||
self._closed = False
|
self._closed: bool = False
|
||||||
self._ready = asyncio.Event()
|
self._ready: asyncio.Event = asyncio.Event()
|
||||||
self._connection._get_websocket = self._get_websocket
|
self._connection._get_websocket = self._get_websocket
|
||||||
self._connection._get_client = lambda: self
|
self._connection._get_client = lambda: self
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
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:
|
||||||
return self.ws
|
return self.ws
|
||||||
|
|
||||||
def _get_state(self, **options):
|
def _get_state(self, **options: Any) -> ConnectionState:
|
||||||
return ConnectionState(dispatch=self.dispatch, handlers=self._handlers,
|
return ConnectionState(dispatch=self.dispatch, handlers=self._handlers,
|
||||||
hooks=self._hooks, http=self.http, loop=self.loop, **options)
|
hooks=self._hooks, http=self.http, loop=self.loop, **options)
|
||||||
|
|
||||||
def _handle_ready(self):
|
def _handle_ready(self) -> None:
|
||||||
self._ready.set()
|
self._ready.set()
|
||||||
|
|
||||||
@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 could be referred to as the Discord WebSocket protocol latency.
|
This could be referred to as the Discord WebSocket protocol latency.
|
||||||
@@ -234,7 +262,7 @@ class Client:
|
|||||||
ws = self.ws
|
ws = self.ws
|
||||||
return float('nan') if not ws else ws.latency
|
return float('nan') if not ws else 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
|
||||||
@@ -247,22 +275,30 @@ class Client:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self):
|
def user(self) -> Optional[ClientUser]:
|
||||||
"""Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in."""
|
"""Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in."""
|
||||||
return self._connection.user
|
return self._connection.user
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guilds(self):
|
def guilds(self) -> List[Guild]:
|
||||||
"""List[:class:`.Guild`]: The guilds that the connected client is a member of."""
|
"""List[:class:`.Guild`]: The guilds that the connected client is a member of."""
|
||||||
return self._connection.guilds
|
return self._connection.guilds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def emojis(self):
|
def emojis(self) -> List[Emoji]:
|
||||||
"""List[:class:`.Emoji`]: The emojis that the connected client has."""
|
"""List[:class:`.Emoji`]: The emojis that the connected client has."""
|
||||||
return self._connection.emojis
|
return self._connection.emojis
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cached_messages(self):
|
def stickers(self) -> List[GuildSticker]:
|
||||||
|
"""List[:class:`.GuildSticker`]: The stickers that the connected client has.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
return self._connection.stickers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached_messages(self) -> Sequence[Message]:
|
||||||
"""Sequence[:class:`.Message`]: Read-only list of messages the connected client has cached.
|
"""Sequence[:class:`.Message`]: Read-only list of messages the connected client has cached.
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
.. versionadded:: 1.1
|
||||||
@@ -270,7 +306,7 @@ class Client:
|
|||||||
return utils.SequenceProxy(self._connection._messages or [])
|
return utils.SequenceProxy(self._connection._messages or [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_channels(self):
|
def private_channels(self) -> List[PrivateChannel]:
|
||||||
"""List[:class:`.abc.PrivateChannel`]: The private channels that the connected client is participating on.
|
"""List[:class:`.abc.PrivateChannel`]: The private channels that the connected client is participating on.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -281,7 +317,7 @@ class Client:
|
|||||||
return self._connection.private_channels
|
return self._connection.private_channels
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def voice_clients(self):
|
def voice_clients(self) -> List[VoiceProtocol]:
|
||||||
"""List[:class:`.VoiceProtocol`]: Represents a list of voice connections.
|
"""List[:class:`.VoiceProtocol`]: Represents a list of voice connections.
|
||||||
|
|
||||||
These are usually :class:`.VoiceClient` instances.
|
These are usually :class:`.VoiceClient` instances.
|
||||||
@@ -289,12 +325,14 @@ class Client:
|
|||||||
return self._connection.voice_clients
|
return self._connection.voice_clients
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def application_id(self):
|
def application_id(self) -> Optional[int]:
|
||||||
"""Optional[:class:`int`]: The client's application ID.
|
"""Optional[:class:`int`]: The client's application ID.
|
||||||
|
|
||||||
If this is not passed via ``__init__`` then this is retrieved
|
If this is not passed via ``__init__`` then this is retrieved
|
||||||
through the gateway when an event contains the data. Usually
|
through the gateway when an event contains the data. Usually
|
||||||
after :func:`~discord.on_connect` is called.
|
after :func:`~discord.on_connect` is called.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
return self._connection.application_id
|
return self._connection.application_id
|
||||||
|
|
||||||
@@ -302,15 +340,15 @@ class Client:
|
|||||||
def application_flags(self) -> ApplicationFlags:
|
def application_flags(self) -> ApplicationFlags:
|
||||||
""":class:`~discord.ApplicationFlags`: The client's application flags.
|
""":class:`~discord.ApplicationFlags`: The client's application flags.
|
||||||
|
|
||||||
.. versionadded: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
return self._connection.application_flags # type: ignore
|
return self._connection.application_flags # type: ignore
|
||||||
|
|
||||||
def is_ready(self):
|
def is_ready(self) -> bool:
|
||||||
""":class:`bool`: Specifies if the client's internal cache is ready for use."""
|
""":class:`bool`: Specifies if the client's internal cache is ready for use."""
|
||||||
return self._ready.is_set()
|
return self._ready.is_set()
|
||||||
|
|
||||||
async def _run_event(self, coro, event_name, *args, **kwargs):
|
async def _run_event(self, coro: Callable[..., Coroutine[Any, Any, Any]], event_name: str, *args: Any, **kwargs: Any) -> None:
|
||||||
try:
|
try:
|
||||||
await coro(*args, **kwargs)
|
await coro(*args, **kwargs)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@@ -321,13 +359,13 @@ class Client:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _schedule_event(self, coro, event_name, *args, **kwargs):
|
def _schedule_event(self, coro: Callable[..., Coroutine[Any, Any, Any]], event_name: str, *args: Any, **kwargs: Any) -> asyncio.Task:
|
||||||
wrapped = self._run_event(coro, event_name, *args, **kwargs)
|
wrapped = self._run_event(coro, event_name, *args, **kwargs)
|
||||||
# Schedules the task
|
# Schedules the task
|
||||||
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, *args, **kwargs):
|
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)
|
||||||
@@ -366,7 +404,7 @@ class Client:
|
|||||||
else:
|
else:
|
||||||
self._schedule_event(coro, method, *args, **kwargs)
|
self._schedule_event(coro, method, *args, **kwargs)
|
||||||
|
|
||||||
async def on_error(self, event_method, *args, **kwargs):
|
async def on_error(self, event_method: str, *args: Any, **kwargs: Any) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
The default error handler provided by the client.
|
The default error handler provided by the client.
|
||||||
@@ -380,13 +418,13 @@ class Client:
|
|||||||
|
|
||||||
# hooks
|
# hooks
|
||||||
|
|
||||||
async def _call_before_identify_hook(self, shard_id, *, initial=False):
|
async def _call_before_identify_hook(self, shard_id: Optional[int], *, initial: bool = False) -> None:
|
||||||
# This hook is an internal hook that actually calls the public one.
|
# This hook is an internal hook that actually calls the public one.
|
||||||
# It allows the library to have its own hook without stepping on the
|
# It allows the library to have its own hook without stepping on the
|
||||||
# toes of those who need to override their own hook.
|
# toes of those who need to override their own hook.
|
||||||
await self.before_identify_hook(shard_id, initial=initial)
|
await self.before_identify_hook(shard_id, initial=initial)
|
||||||
|
|
||||||
async def before_identify_hook(self, shard_id, *, initial=False):
|
async def before_identify_hook(self, shard_id: Optional[int], *, initial: bool = False) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
A hook that is called before IDENTIFYing a session. This is useful
|
A hook that is called before IDENTIFYing a session. This is useful
|
||||||
@@ -410,7 +448,7 @@ class Client:
|
|||||||
|
|
||||||
# login state management
|
# login state management
|
||||||
|
|
||||||
async def login(self, token):
|
async def login(self, token: str) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Logs in the client with the specified credentials.
|
Logs in the client with the specified credentials.
|
||||||
@@ -432,10 +470,12 @@ class Client:
|
|||||||
passing status code.
|
passing status code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
log.info('logging in using static token')
|
_log.info('logging in using static token')
|
||||||
await self.http.static_login(token.strip())
|
|
||||||
|
|
||||||
async def connect(self, *, reconnect=True):
|
data = await self.http.static_login(token.strip())
|
||||||
|
self._connection.user = ClientUser(state=self._connection, data=data)
|
||||||
|
|
||||||
|
async def connect(self, *, reconnect: bool = True) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a websocket connection and lets the websocket listen
|
Creates a websocket connection and lets the websocket listen
|
||||||
@@ -473,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
|
||||||
@@ -512,14 +552,14 @@ 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.
|
||||||
# This is apparently what the official Discord client does.
|
# This is apparently what the official Discord client does.
|
||||||
ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id)
|
ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id)
|
||||||
|
|
||||||
async def close(self):
|
async def close(self) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Closes the connection to Discord.
|
Closes the connection to Discord.
|
||||||
@@ -531,7 +571,7 @@ class Client:
|
|||||||
|
|
||||||
for voice in self.voice_clients:
|
for voice in self.voice_clients:
|
||||||
try:
|
try:
|
||||||
await voice.disconnect()
|
await voice.disconnect(force=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
# if an error happens during disconnects, disregard it.
|
# if an error happens during disconnects, disregard it.
|
||||||
pass
|
pass
|
||||||
@@ -542,7 +582,7 @@ class Client:
|
|||||||
await self.http.close()
|
await self.http.close()
|
||||||
self._ready.clear()
|
self._ready.clear()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""Clears the internal state of the bot.
|
"""Clears the internal state of the bot.
|
||||||
|
|
||||||
After this, the bot can be considered "re-opened", i.e. :meth:`is_closed`
|
After this, the bot can be considered "re-opened", i.e. :meth:`is_closed`
|
||||||
@@ -554,7 +594,7 @@ class Client:
|
|||||||
self._connection.clear()
|
self._connection.clear()
|
||||||
self.http.recreate()
|
self.http.recreate()
|
||||||
|
|
||||||
async def start(self, token, *, reconnect=True):
|
async def start(self, token: str, *, reconnect: bool = True) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
A shorthand coroutine for :meth:`login` + :meth:`connect`.
|
A shorthand coroutine for :meth:`login` + :meth:`connect`.
|
||||||
@@ -567,7 +607,7 @@ class Client:
|
|||||||
await self.login(token)
|
await self.login(token)
|
||||||
await self.connect(reconnect=reconnect)
|
await self.connect(reconnect=reconnect)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""A blocking call that abstracts away the event loop
|
"""A blocking call that abstracts away the event loop
|
||||||
initialisation from you.
|
initialisation from you.
|
||||||
|
|
||||||
@@ -614,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():
|
||||||
@@ -629,28 +669,49 @@ class Client:
|
|||||||
|
|
||||||
# properties
|
# properties
|
||||||
|
|
||||||
def is_closed(self):
|
def is_closed(self) -> bool:
|
||||||
""":class:`bool`: Indicates if the websocket connection is closed."""
|
""":class:`bool`: Indicates if the websocket connection is closed."""
|
||||||
return self._closed
|
return self._closed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self):
|
def activity(self) -> Optional[ActivityTypes]:
|
||||||
"""Optional[:class:`.BaseActivity`]: The activity being used upon
|
"""Optional[:class:`.BaseActivity`]: The activity being used upon
|
||||||
logging in.
|
logging in.
|
||||||
"""
|
"""
|
||||||
return create_activity(self._connection._activity)
|
return create_activity(self._connection._activity)
|
||||||
|
|
||||||
@activity.setter
|
@activity.setter
|
||||||
def activity(self, value):
|
def activity(self, value: Optional[ActivityTypes]) -> None:
|
||||||
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
|
@property
|
||||||
def allowed_mentions(self):
|
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
|
||||||
|
def allowed_mentions(self) -> Optional[AllowedMentions]:
|
||||||
"""Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration.
|
"""Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration.
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
@@ -658,14 +719,14 @@ class Client:
|
|||||||
return self._connection.allowed_mentions
|
return self._connection.allowed_mentions
|
||||||
|
|
||||||
@allowed_mentions.setter
|
@allowed_mentions.setter
|
||||||
def allowed_mentions(self, value):
|
def allowed_mentions(self, value: Optional[AllowedMentions]) -> None:
|
||||||
if value is None or isinstance(value, AllowedMentions):
|
if value is None or isinstance(value, AllowedMentions):
|
||||||
self._connection.allowed_mentions = value
|
self._connection.allowed_mentions = value
|
||||||
else:
|
else:
|
||||||
raise TypeError(f'allowed_mentions must be AllowedMentions not {value.__class__!r}')
|
raise TypeError(f'allowed_mentions must be AllowedMentions not {value.__class__!r}')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def intents(self):
|
def intents(self) -> Intents:
|
||||||
""":class:`~discord.Intents`: The intents configured for this connection.
|
""":class:`~discord.Intents`: The intents configured for this connection.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
@@ -675,12 +736,12 @@ class Client:
|
|||||||
# helpers/getters
|
# helpers/getters
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def users(self):
|
def users(self) -> List[User]:
|
||||||
"""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):
|
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
|
||||||
-----------
|
-----------
|
||||||
@@ -689,12 +750,34 @@ 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)
|
||||||
|
|
||||||
def get_stage_instance(self, id) -> Optional[StageInstance]:
|
def get_partial_messageable(self, id: int, *, type: Optional[ChannelType] = None) -> PartialMessageable:
|
||||||
|
"""Returns a partial messageable with the given channel ID.
|
||||||
|
|
||||||
|
This is useful if you have a channel_id but don't want to do an API call
|
||||||
|
to send messages to it.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
id: :class:`int`
|
||||||
|
The channel ID to create a partial messageable for.
|
||||||
|
type: Optional[:class:`.ChannelType`]
|
||||||
|
The underlying channel type for the partial messageable.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`.PartialMessageable`
|
||||||
|
The partial messageable
|
||||||
|
"""
|
||||||
|
return PartialMessageable(state=self._connection, id=id, type=type)
|
||||||
|
|
||||||
|
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
|
||||||
@@ -706,7 +789,7 @@ class Client:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
Optional[:class:`StageInstance`]
|
Optional[:class:`.StageInstance`]
|
||||||
The returns stage instance of ``None`` if not found.
|
The returns stage instance of ``None`` if not found.
|
||||||
"""
|
"""
|
||||||
from .channel import StageChannel
|
from .channel import StageChannel
|
||||||
@@ -716,7 +799,7 @@ class Client:
|
|||||||
if isinstance(channel, StageChannel):
|
if isinstance(channel, StageChannel):
|
||||||
return channel.instance
|
return channel.instance
|
||||||
|
|
||||||
def get_guild(self, id):
|
def get_guild(self, id: int, /) -> Optional[Guild]:
|
||||||
"""Returns a guild with the given ID.
|
"""Returns a guild with the given ID.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -731,7 +814,7 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return self._connection._get_guild(id)
|
return self._connection._get_guild(id)
|
||||||
|
|
||||||
def get_user(self, id):
|
def get_user(self, id: int, /) -> Optional[User]:
|
||||||
"""Returns a user with the given ID.
|
"""Returns a user with the given ID.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -746,7 +829,7 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return self._connection.get_user(id)
|
return self._connection.get_user(id)
|
||||||
|
|
||||||
def get_emoji(self, id):
|
def get_emoji(self, id: int, /) -> Optional[Emoji]:
|
||||||
"""Returns an emoji with the given ID.
|
"""Returns an emoji with the given ID.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -761,7 +844,24 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return self._connection.get_emoji(id)
|
return self._connection.get_emoji(id)
|
||||||
|
|
||||||
def get_all_channels(self):
|
def get_sticker(self, id: int, /) -> Optional[GuildSticker]:
|
||||||
|
"""Returns a guild sticker with the given ID.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
To retrieve standard stickers, use :meth:`.fetch_sticker`.
|
||||||
|
or :meth:`.fetch_premium_sticker_packs`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Optional[:class:`.GuildSticker`]
|
||||||
|
The sticker or ``None`` if not found.
|
||||||
|
"""
|
||||||
|
return self._connection.get_sticker(id)
|
||||||
|
|
||||||
|
def get_all_channels(self) -> Generator[GuildChannel, None, None]:
|
||||||
"""A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'.
|
"""A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'.
|
||||||
|
|
||||||
This is equivalent to: ::
|
This is equivalent to: ::
|
||||||
@@ -785,7 +885,7 @@ class Client:
|
|||||||
for guild in self.guilds:
|
for guild in self.guilds:
|
||||||
yield from guild.channels
|
yield from guild.channels
|
||||||
|
|
||||||
def get_all_members(self):
|
def get_all_members(self) -> Generator[Member, None, None]:
|
||||||
"""Returns a generator with every :class:`.Member` the client can see.
|
"""Returns a generator with every :class:`.Member` the client can see.
|
||||||
|
|
||||||
This is equivalent to: ::
|
This is equivalent to: ::
|
||||||
@@ -804,14 +904,20 @@ class Client:
|
|||||||
|
|
||||||
# listeners/waiters
|
# listeners/waiters
|
||||||
|
|
||||||
async def wait_until_ready(self):
|
async def wait_until_ready(self) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Waits until the client's internal cache is all ready.
|
Waits until the client's internal cache is all ready.
|
||||||
"""
|
"""
|
||||||
await self._ready.wait()
|
await self._ready.wait()
|
||||||
|
|
||||||
def wait_for(self, event, *, check=None, timeout=None):
|
def wait_for(
|
||||||
|
self,
|
||||||
|
event: str,
|
||||||
|
*,
|
||||||
|
check: Optional[Callable[..., bool]] = None,
|
||||||
|
timeout: Optional[float] = None,
|
||||||
|
) -> Any:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Waits for a WebSocket event to be dispatched.
|
Waits for a WebSocket event to be dispatched.
|
||||||
@@ -911,7 +1017,7 @@ class Client:
|
|||||||
|
|
||||||
# event registration
|
# event registration
|
||||||
|
|
||||||
def event(self, coro):
|
def event(self, coro: Coro) -> Coro:
|
||||||
"""A decorator that registers an event to listen to.
|
"""A decorator that registers an event to listen to.
|
||||||
|
|
||||||
You can find more info about the events on the :ref:`documentation below <discord-api-events>`.
|
You can find more info about the events on the :ref:`documentation below <discord-api-events>`.
|
||||||
@@ -937,10 +1043,15 @@ 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(self, *, activity=None, status=None, afk=False):
|
async def change_presence(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
activity: Optional[BaseActivity] = None,
|
||||||
|
status: Optional[Status] = None,
|
||||||
|
):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Changes the client's presence.
|
Changes the client's presence.
|
||||||
@@ -953,6 +1064,9 @@ class Client:
|
|||||||
game = discord.Game("with the API")
|
game = discord.Game("with the API")
|
||||||
await client.change_presence(status=discord.Status.idle, activity=game)
|
await client.change_presence(status=discord.Status.idle, activity=game)
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Removed the ``afk`` keyword-only parameter.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
activity: Optional[:class:`.BaseActivity`]
|
activity: Optional[:class:`.BaseActivity`]
|
||||||
@@ -960,10 +1074,6 @@ class Client:
|
|||||||
status: Optional[:class:`.Status`]
|
status: Optional[:class:`.Status`]
|
||||||
Indicates what status to change to. If ``None``, then
|
Indicates what status to change to. If ``None``, then
|
||||||
:attr:`.Status.online` is used.
|
:attr:`.Status.online` is used.
|
||||||
afk: Optional[:class:`bool`]
|
|
||||||
Indicates if you are going AFK. This allows the discord
|
|
||||||
client to know how to handle push notifications better
|
|
||||||
for you in case you are actually idle and not lying.
|
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
@@ -972,16 +1082,15 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if status is None:
|
if status is None:
|
||||||
status = 'online'
|
status_str = 'online'
|
||||||
status_enum = Status.online
|
status = Status.online
|
||||||
elif status is Status.offline:
|
elif status is Status.offline:
|
||||||
status = 'invisible'
|
status_str = 'invisible'
|
||||||
status_enum = Status.offline
|
status = Status.offline
|
||||||
else:
|
else:
|
||||||
status_enum = status
|
status_str = str(status)
|
||||||
status = str(status)
|
|
||||||
|
|
||||||
await self.ws.change_presence(activity=activity, status=status, afk=afk)
|
await self.ws.change_presence(activity=activity, status=status_str)
|
||||||
|
|
||||||
for guild in self._connection.guilds:
|
for guild in self._connection.guilds:
|
||||||
me = guild.me
|
me = guild.me
|
||||||
@@ -993,11 +1102,17 @@ class Client:
|
|||||||
else:
|
else:
|
||||||
me.activities = ()
|
me.activities = ()
|
||||||
|
|
||||||
me.status = status_enum
|
me.status = status
|
||||||
|
|
||||||
# Guild stuff
|
# Guild stuff
|
||||||
|
|
||||||
def fetch_guilds(self, *, limit: int = 100, before: SnowflakeTime = None, after: SnowflakeTime = None) -> List[Guild]:
|
def fetch_guilds(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
limit: Optional[int] = 100,
|
||||||
|
before: SnowflakeTime = None,
|
||||||
|
after: SnowflakeTime = None
|
||||||
|
) -> GuildIterator:
|
||||||
"""Retrieves an :class:`.AsyncIterator` that enables receiving your guilds.
|
"""Retrieves an :class:`.AsyncIterator` that enables receiving your guilds.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -1052,7 +1167,7 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return GuildIterator(self, limit=limit, before=before, after=after)
|
return GuildIterator(self, limit=limit, before=before, after=after)
|
||||||
|
|
||||||
async def fetch_template(self, code):
|
async def fetch_template(self, code: Union[Template, str]) -> Template:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Gets a :class:`.Template` from a discord.new URL or code.
|
Gets a :class:`.Template` from a discord.new URL or code.
|
||||||
@@ -1078,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):
|
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.
|
||||||
@@ -1112,7 +1227,14 @@ class Client:
|
|||||||
data = await self.http.get_guild(guild_id)
|
data = await self.http.get_guild(guild_id)
|
||||||
return Guild(data=data, state=self._connection)
|
return Guild(data=data, state=self._connection)
|
||||||
|
|
||||||
async def create_guild(self, name: str, region: Optional[VoiceRegion] = None, icon: Any = None, *, code: str = None):
|
async def create_guild(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str,
|
||||||
|
region: Union[VoiceRegion, str] = VoiceRegion.us_west,
|
||||||
|
icon: bytes = MISSING,
|
||||||
|
code: str = MISSING,
|
||||||
|
) -> Guild:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a :class:`.Guild`.
|
Creates a :class:`.Guild`.
|
||||||
@@ -1126,10 +1248,10 @@ class Client:
|
|||||||
region: :class:`.VoiceRegion`
|
region: :class:`.VoiceRegion`
|
||||||
The region for the voice communication server.
|
The region for the voice communication server.
|
||||||
Defaults to :attr:`.VoiceRegion.us_west`.
|
Defaults to :attr:`.VoiceRegion.us_west`.
|
||||||
icon: :class:`bytes`
|
icon: Optional[:class:`bytes`]
|
||||||
The :term:`py:bytes-like object` representing the icon. See :meth:`.ClientUser.edit`
|
The :term:`py:bytes-like object` representing the icon. See :meth:`.ClientUser.edit`
|
||||||
for more details on what is expected.
|
for more details on what is expected.
|
||||||
code: Optional[:class:`str`]
|
code: :class:`str`
|
||||||
The code for a template to create the guild with.
|
The code for a template to create the guild with.
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
@@ -1147,22 +1269,23 @@ class Client:
|
|||||||
The guild created. This is not the same guild that is
|
The guild created. This is not the same guild that is
|
||||||
added to cache.
|
added to cache.
|
||||||
"""
|
"""
|
||||||
if icon is not None:
|
if icon is not MISSING:
|
||||||
icon = utils._bytes_to_base64_data(icon)
|
icon_base64 = utils._bytes_to_base64_data(icon)
|
||||||
|
else:
|
||||||
|
icon_base64 = None
|
||||||
|
|
||||||
region = region or VoiceRegion.us_west
|
region_value = str(region)
|
||||||
region_value = region.value
|
|
||||||
|
|
||||||
if code:
|
if code:
|
||||||
data = await self.http.create_from_template(code, name, region_value, icon)
|
data = await self.http.create_from_template(code, name, region_value, icon_base64)
|
||||||
else:
|
else:
|
||||||
data = await self.http.create_guild(name, region_value, icon)
|
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.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
@@ -1180,7 +1303,7 @@ class Client:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
:class:`StageInstance`
|
:class:`.StageInstance`
|
||||||
The stage instance from the stage channel ID.
|
The stage instance from the stage channel ID.
|
||||||
"""
|
"""
|
||||||
data = await self.http.get_stage_instance(channel_id)
|
data = await self.http.get_stage_instance(channel_id)
|
||||||
@@ -1259,7 +1382,7 @@ class Client:
|
|||||||
|
|
||||||
# Miscellaneous stuff
|
# Miscellaneous stuff
|
||||||
|
|
||||||
async def fetch_widget(self, guild_id):
|
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.
|
||||||
@@ -1289,7 +1412,7 @@ class Client:
|
|||||||
|
|
||||||
return Widget(state=self._connection, data=data)
|
return Widget(state=self._connection, data=data)
|
||||||
|
|
||||||
async def application_info(self):
|
async def application_info(self) -> AppInfo:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves the bot's application information.
|
Retrieves the bot's application information.
|
||||||
@@ -1309,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):
|
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.
|
||||||
@@ -1340,10 +1463,10 @@ 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):
|
async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, PrivateChannel, Thread]:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves a :class:`.abc.GuildChannel` or :class:`.abc.PrivateChannel` with the specified ID.
|
Retrieves a :class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, or :class:`.Thread` with the specified ID.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -1364,25 +1487,28 @@ class Client:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
Union[:class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`]
|
Union[:class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, :class:`.Thread`]
|
||||||
The channel from the ID.
|
The channel from the ID.
|
||||||
"""
|
"""
|
||||||
data = await self.http.get_channel(channel_id)
|
data = await self.http.get_channel(channel_id)
|
||||||
|
|
||||||
factory, ch_type = _channel_factory(data['type'])
|
factory, ch_type = _threaded_channel_factory(data['type'])
|
||||||
if factory is None:
|
if factory is None:
|
||||||
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(data))
|
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(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):
|
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.
|
||||||
@@ -1404,7 +1530,50 @@ 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 create_dm(self, user):
|
async def fetch_sticker(self, sticker_id: int, /) -> Union[StandardSticker, GuildSticker]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves a :class:`.Sticker` with the specified ID.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
:exc:`.HTTPException`
|
||||||
|
Retrieving the sticker failed.
|
||||||
|
:exc:`.NotFound`
|
||||||
|
Invalid sticker ID.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Union[:class:`.StandardSticker`, :class:`.GuildSticker`]
|
||||||
|
The sticker you requested.
|
||||||
|
"""
|
||||||
|
data = await self.http.get_sticker(sticker_id)
|
||||||
|
cls, _ = _sticker_factory(data['type']) # type: ignore
|
||||||
|
return cls(state=self._connection, data=data) # type: ignore
|
||||||
|
|
||||||
|
async def fetch_premium_sticker_packs(self) -> List[StickerPack]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves all available premium sticker packs.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
:exc:`.HTTPException`
|
||||||
|
Retrieving the sticker packs failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
List[:class:`.StickerPack`]
|
||||||
|
All available premium sticker packs.
|
||||||
|
"""
|
||||||
|
data = await self.http.list_premium_sticker_packs()
|
||||||
|
return [StickerPack(state=self._connection, data=pack) for pack in data['sticker_packs']]
|
||||||
|
|
||||||
|
async def create_dm(self, user: Snowflake) -> DMChannel:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a :class:`.DMChannel` with this user.
|
Creates a :class:`.DMChannel` with this user.
|
||||||
@@ -1438,6 +1607,8 @@ class Client:
|
|||||||
This method should be used for when a view is comprised of components
|
This method should be used for when a view is comprised of components
|
||||||
that last longer than the lifecycle of the program.
|
that last longer than the lifecycle of the program.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
view: :class:`discord.ui.View`
|
view: :class:`discord.ui.View`
|
||||||
@@ -1466,5 +1637,8 @@ class Client:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def persistent_views(self) -> Sequence[View]:
|
def persistent_views(self) -> Sequence[View]:
|
||||||
"""Sequence[:class:`View`]: A sequence of persistent views added to the client."""
|
"""Sequence[:class:`.View`]: A sequence of persistent views added to the client.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
return self._connection.persistent_views
|
return self._connection.persistent_views
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -132,11 +132,16 @@ class Button(Component):
|
|||||||
|
|
||||||
This inherits from :class:`Component`.
|
This inherits from :class:`Component`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The user constructible and usable type to create a button is :class:`discord.ui.Button`
|
||||||
|
not this one.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
style: :class:`ComponentButtonStyle`
|
style: :class:`.ButtonStyle`
|
||||||
The style of the button.
|
The style of the button.
|
||||||
custom_id: Optional[:class:`str`]
|
custom_id: Optional[:class:`str`]
|
||||||
The ID of the button that gets received during an interaction.
|
The ID of the button that gets received during an interaction.
|
||||||
@@ -200,6 +205,11 @@ class SelectMenu(Component):
|
|||||||
A select menu is functionally the same as a dropdown, however
|
A select menu is functionally the same as a dropdown, however
|
||||||
on mobile it renders a bit differently.
|
on mobile it renders a bit differently.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The user constructible and usable type to create a select menu is
|
||||||
|
:class:`discord.ui.Select` not this one.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
@@ -216,6 +226,8 @@ class SelectMenu(Component):
|
|||||||
Defaults to 1 and must be between 1 and 25.
|
Defaults to 1 and must be between 1 and 25.
|
||||||
options: List[:class:`SelectOption`]
|
options: List[:class:`SelectOption`]
|
||||||
A list of options that can be selected in this menu.
|
A list of options that can be selected in this menu.
|
||||||
|
disabled: :class:`bool`
|
||||||
|
Whether the select is disabled or not.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__: Tuple[str, ...] = (
|
__slots__: Tuple[str, ...] = (
|
||||||
@@ -224,6 +236,7 @@ class SelectMenu(Component):
|
|||||||
'min_values',
|
'min_values',
|
||||||
'max_values',
|
'max_values',
|
||||||
'options',
|
'options',
|
||||||
|
'disabled',
|
||||||
)
|
)
|
||||||
|
|
||||||
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
|
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
|
||||||
@@ -235,6 +248,7 @@ class SelectMenu(Component):
|
|||||||
self.min_values: int = data.get('min_values', 1)
|
self.min_values: int = data.get('min_values', 1)
|
||||||
self.max_values: int = data.get('max_values', 1)
|
self.max_values: int = data.get('max_values', 1)
|
||||||
self.options: List[SelectOption] = [SelectOption.from_dict(option) for option in data.get('options', [])]
|
self.options: List[SelectOption] = [SelectOption.from_dict(option) for option in data.get('options', [])]
|
||||||
|
self.disabled: bool = data.get('disabled', False)
|
||||||
|
|
||||||
def to_dict(self) -> SelectMenuPayload:
|
def to_dict(self) -> SelectMenuPayload:
|
||||||
payload: SelectMenuPayload = {
|
payload: SelectMenuPayload = {
|
||||||
@@ -243,6 +257,7 @@ class SelectMenu(Component):
|
|||||||
'min_values': self.min_values,
|
'min_values': self.min_values,
|
||||||
'max_values': self.max_values,
|
'max_values': self.max_values,
|
||||||
'options': [op.to_dict() for op in self.options],
|
'options': [op.to_dict() for op in self.options],
|
||||||
|
'disabled': self.disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.placeholder:
|
if self.placeholder:
|
||||||
@@ -262,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`
|
||||||
@@ -314,6 +329,16 @@ class SelectOption:
|
|||||||
f'emoji={self.emoji!r} default={self.default!r}>'
|
f'emoji={self.emoji!r} default={self.default!r}>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self.emoji:
|
||||||
|
base = f'{self.emoji} {self.label}'
|
||||||
|
else:
|
||||||
|
base = self.label
|
||||||
|
|
||||||
|
if self.description:
|
||||||
|
return f'{base}\n{self.description}'
|
||||||
|
return base
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: SelectOptionPayload) -> SelectOption:
|
def from_dict(cls, data: SelectOptionPayload) -> SelectOption:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -202,12 +208,10 @@ class Embed:
|
|||||||
self.url = str(self.url)
|
self.url = str(self.url)
|
||||||
|
|
||||||
if timestamp:
|
if timestamp:
|
||||||
if timestamp.tzinfo is None:
|
|
||||||
timestamp = timestamp.astimezone()
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -327,7 +331,11 @@ class Embed:
|
|||||||
|
|
||||||
@timestamp.setter
|
@timestamp.setter
|
||||||
def timestamp(self, value: MaybeEmpty[datetime.datetime]):
|
def timestamp(self, value: MaybeEmpty[datetime.datetime]):
|
||||||
if isinstance(value, (datetime.datetime, _EmptyEmbed)):
|
if isinstance(value, datetime.datetime):
|
||||||
|
if value.tzinfo is None:
|
||||||
|
value = value.astimezone()
|
||||||
|
self._timestamp = value
|
||||||
|
elif isinstance(value, _EmptyEmbed):
|
||||||
self._timestamp = value
|
self._timestamp = value
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead")
|
raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead")
|
||||||
@@ -395,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.
|
||||||
|
|
||||||
@@ -411,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
|
||||||
|
|
||||||
@@ -437,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
|
||||||
@@ -451,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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
101
discord/enums.py
101
discord/enums.py
@@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
import types
|
import types
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import Any, Dict, Optional, TYPE_CHECKING, Type, TypeVar
|
from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Type, TypeVar
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Enum',
|
'Enum',
|
||||||
@@ -46,6 +46,7 @@ __all__ = (
|
|||||||
'ExpireBehaviour',
|
'ExpireBehaviour',
|
||||||
'ExpireBehavior',
|
'ExpireBehavior',
|
||||||
'StickerType',
|
'StickerType',
|
||||||
|
'StickerFormatType',
|
||||||
'InviteTarget',
|
'InviteTarget',
|
||||||
'VideoQualityMode',
|
'VideoQualityMode',
|
||||||
'ComponentType',
|
'ComponentType',
|
||||||
@@ -56,22 +57,35 @@ __all__ = (
|
|||||||
'NSFWLevel',
|
'NSFWLevel',
|
||||||
)
|
)
|
||||||
|
|
||||||
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__')
|
||||||
|
|
||||||
|
|
||||||
class EnumMeta(type):
|
class EnumMeta(type):
|
||||||
def __new__(cls, name, bases, attrs):
|
if TYPE_CHECKING:
|
||||||
|
__name__: ClassVar[str]
|
||||||
|
_enum_member_names_: ClassVar[List[str]]
|
||||||
|
_enum_member_map_: ClassVar[Dict[str, Any]]
|
||||||
|
_enum_value_map_: ClassVar[Dict[Any, Any]]
|
||||||
|
|
||||||
|
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:
|
||||||
@@ -101,7 +115,7 @@ class EnumMeta(type):
|
|||||||
attrs['_enum_member_names_'] = member_names
|
attrs['_enum_member_names_'] = member_names
|
||||||
attrs['_enum_value_cls_'] = value_cls
|
attrs['_enum_value_cls_'] = value_cls
|
||||||
actual_cls = super().__new__(cls, name, bases, attrs)
|
actual_cls = super().__new__(cls, name, bases, attrs)
|
||||||
value_cls._actual_enum_cls_ = actual_cls
|
value_cls._actual_enum_cls_ = actual_cls # type: ignore
|
||||||
return actual_cls
|
return actual_cls
|
||||||
|
|
||||||
def __iter__(cls):
|
def __iter__(cls):
|
||||||
@@ -143,9 +157,11 @@ class EnumMeta(type):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class Enum(metaclass=EnumMeta):
|
class Enum(metaclass=EnumMeta):
|
||||||
@classmethod
|
@classmethod
|
||||||
def try_value(cls, value):
|
def try_value(cls, value):
|
||||||
@@ -154,6 +170,7 @@ else:
|
|||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ChannelType(Enum):
|
class ChannelType(Enum):
|
||||||
text = 0
|
text = 0
|
||||||
private = 1
|
private = 1
|
||||||
@@ -170,6 +187,7 @@ class ChannelType(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class MessageType(Enum):
|
class MessageType(Enum):
|
||||||
default = 0
|
default = 0
|
||||||
recipient_add = 1
|
recipient_add = 1
|
||||||
@@ -195,6 +213,7 @@ class MessageType(Enum):
|
|||||||
thread_starter_message = 21
|
thread_starter_message = 21
|
||||||
guild_invite_reminder = 22
|
guild_invite_reminder = 22
|
||||||
|
|
||||||
|
|
||||||
class VoiceRegion(Enum):
|
class VoiceRegion(Enum):
|
||||||
us_west = 'us-west'
|
us_west = 'us-west'
|
||||||
us_east = 'us-east'
|
us_east = 'us-east'
|
||||||
@@ -223,6 +242,7 @@ class VoiceRegion(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class SpeakingState(Enum):
|
class SpeakingState(Enum):
|
||||||
none = 0
|
none = 0
|
||||||
voice = 1
|
voice = 1
|
||||||
@@ -235,7 +255,8 @@ class SpeakingState(Enum):
|
|||||||
def __int__(self):
|
def __int__(self):
|
||||||
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
|
||||||
@@ -245,7 +266,8 @@ class VerificationLevel(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
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
|
||||||
@@ -253,6 +275,7 @@ class ContentFilter(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Status(Enum):
|
class Status(Enum):
|
||||||
online = 'online'
|
online = 'online'
|
||||||
offline = 'offline'
|
offline = 'offline'
|
||||||
@@ -264,6 +287,7 @@ class Status(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class DefaultAvatar(Enum):
|
class DefaultAvatar(Enum):
|
||||||
blurple = 0
|
blurple = 0
|
||||||
grey = 1
|
grey = 1
|
||||||
@@ -275,16 +299,20 @@ class DefaultAvatar(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
class AuditLogActionCategory(Enum):
|
class AuditLogActionCategory(Enum):
|
||||||
create = 1
|
create = 1
|
||||||
delete = 2
|
delete = 2
|
||||||
update = 3
|
update = 3
|
||||||
|
|
||||||
|
|
||||||
class AuditLogAction(Enum):
|
class AuditLogAction(Enum):
|
||||||
|
# fmt: off
|
||||||
guild_update = 1
|
guild_update = 1
|
||||||
channel_create = 10
|
channel_create = 10
|
||||||
channel_update = 11
|
channel_update = 11
|
||||||
@@ -323,9 +351,17 @@ class AuditLogAction(Enum):
|
|||||||
stage_instance_create = 83
|
stage_instance_create = 83
|
||||||
stage_instance_update = 84
|
stage_instance_update = 84
|
||||||
stage_instance_delete = 85
|
stage_instance_delete = 85
|
||||||
|
sticker_create = 90
|
||||||
|
sticker_update = 91
|
||||||
|
sticker_delete = 92
|
||||||
|
thread_create = 110
|
||||||
|
thread_update = 111
|
||||||
|
thread_delete = 112
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def category(self) -> Optional[AuditLogActionCategory]:
|
def category(self) -> Optional[AuditLogActionCategory]:
|
||||||
|
# fmt: off
|
||||||
lookup: Dict[AuditLogAction, Optional[AuditLogActionCategory]] = {
|
lookup: Dict[AuditLogAction, Optional[AuditLogActionCategory]] = {
|
||||||
AuditLogAction.guild_update: AuditLogActionCategory.update,
|
AuditLogAction.guild_update: AuditLogActionCategory.update,
|
||||||
AuditLogAction.channel_create: AuditLogActionCategory.create,
|
AuditLogAction.channel_create: AuditLogActionCategory.create,
|
||||||
@@ -365,7 +401,14 @@ class AuditLogAction(Enum):
|
|||||||
AuditLogAction.stage_instance_create: AuditLogActionCategory.create,
|
AuditLogAction.stage_instance_create: AuditLogActionCategory.create,
|
||||||
AuditLogAction.stage_instance_update: AuditLogActionCategory.update,
|
AuditLogAction.stage_instance_update: AuditLogActionCategory.update,
|
||||||
AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete,
|
AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete,
|
||||||
|
AuditLogAction.sticker_create: AuditLogActionCategory.create,
|
||||||
|
AuditLogAction.sticker_update: AuditLogActionCategory.update,
|
||||||
|
AuditLogAction.sticker_delete: AuditLogActionCategory.delete,
|
||||||
|
AuditLogAction.thread_create: AuditLogActionCategory.create,
|
||||||
|
AuditLogAction.thread_update: AuditLogActionCategory.update,
|
||||||
|
AuditLogAction.thread_delete: AuditLogActionCategory.delete,
|
||||||
}
|
}
|
||||||
|
# fmt: on
|
||||||
return lookup[self]
|
return lookup[self]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -395,6 +438,11 @@ class AuditLogAction(Enum):
|
|||||||
return 'integration'
|
return 'integration'
|
||||||
elif v < 90:
|
elif v < 90:
|
||||||
return 'stage_instance'
|
return 'stage_instance'
|
||||||
|
elif v < 93:
|
||||||
|
return 'sticker'
|
||||||
|
elif v < 113:
|
||||||
|
return 'thread'
|
||||||
|
|
||||||
|
|
||||||
class UserFlags(Enum):
|
class UserFlags(Enum):
|
||||||
staff = 1
|
staff = 1
|
||||||
@@ -415,6 +463,7 @@ class UserFlags(Enum):
|
|||||||
verified_bot_developer = 131072
|
verified_bot_developer = 131072
|
||||||
discord_certified_moderator = 262144
|
discord_certified_moderator = 262144
|
||||||
|
|
||||||
|
|
||||||
class ActivityType(Enum):
|
class ActivityType(Enum):
|
||||||
unknown = -1
|
unknown = -1
|
||||||
playing = 0
|
playing = 0
|
||||||
@@ -427,36 +476,60 @@ class ActivityType(Enum):
|
|||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class TeamMembershipState(Enum):
|
class TeamMembershipState(Enum):
|
||||||
invited = 1
|
invited = 1
|
||||||
accepted = 2
|
accepted = 2
|
||||||
|
|
||||||
|
|
||||||
class WebhookType(Enum):
|
class WebhookType(Enum):
|
||||||
incoming = 1
|
incoming = 1
|
||||||
channel_follower = 2
|
channel_follower = 2
|
||||||
application = 3
|
application = 3
|
||||||
|
|
||||||
|
|
||||||
class ExpireBehaviour(Enum):
|
class ExpireBehaviour(Enum):
|
||||||
remove_role = 0
|
remove_role = 0
|
||||||
kick = 1
|
kick = 1
|
||||||
|
|
||||||
|
|
||||||
ExpireBehavior = ExpireBehaviour
|
ExpireBehavior = ExpireBehaviour
|
||||||
|
|
||||||
|
|
||||||
class StickerType(Enum):
|
class StickerType(Enum):
|
||||||
|
standard = 1
|
||||||
|
guild = 2
|
||||||
|
|
||||||
|
|
||||||
|
class StickerFormatType(Enum):
|
||||||
png = 1
|
png = 1
|
||||||
apng = 2
|
apng = 2
|
||||||
lottie = 3
|
lottie = 3
|
||||||
|
|
||||||
|
@property
|
||||||
|
def file_extension(self) -> str:
|
||||||
|
# fmt: off
|
||||||
|
lookup: Dict[StickerFormatType, str] = {
|
||||||
|
StickerFormatType.png: 'png',
|
||||||
|
StickerFormatType.apng: 'png',
|
||||||
|
StickerFormatType.lottie: 'json',
|
||||||
|
}
|
||||||
|
# fmt: on
|
||||||
|
return lookup[self]
|
||||||
|
|
||||||
|
|
||||||
class InviteTarget(Enum):
|
class InviteTarget(Enum):
|
||||||
unknown = 0
|
unknown = 0
|
||||||
stream = 1
|
stream = 1
|
||||||
embedded_application = 2
|
embedded_application = 2
|
||||||
|
|
||||||
|
|
||||||
class InteractionType(Enum):
|
class InteractionType(Enum):
|
||||||
ping = 1
|
ping = 1
|
||||||
application_command = 2
|
application_command = 2
|
||||||
component = 3
|
component = 3
|
||||||
|
|
||||||
|
|
||||||
class InteractionResponseType(Enum):
|
class InteractionResponseType(Enum):
|
||||||
pong = 1
|
pong = 1
|
||||||
# ack = 2 (deprecated)
|
# ack = 2 (deprecated)
|
||||||
@@ -466,6 +539,7 @@ class InteractionResponseType(Enum):
|
|||||||
deferred_message_update = 6 # for components
|
deferred_message_update = 6 # for components
|
||||||
message_update = 7 # for components
|
message_update = 7 # for components
|
||||||
|
|
||||||
|
|
||||||
class VideoQualityMode(Enum):
|
class VideoQualityMode(Enum):
|
||||||
auto = 1
|
auto = 1
|
||||||
full = 2
|
full = 2
|
||||||
@@ -473,6 +547,7 @@ class VideoQualityMode(Enum):
|
|||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class ComponentType(Enum):
|
class ComponentType(Enum):
|
||||||
action_row = 1
|
action_row = 1
|
||||||
button = 2
|
button = 2
|
||||||
@@ -481,6 +556,7 @@ class ComponentType(Enum):
|
|||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class ButtonStyle(Enum):
|
class ButtonStyle(Enum):
|
||||||
primary = 1
|
primary = 1
|
||||||
secondary = 2
|
secondary = 2
|
||||||
@@ -491,30 +567,37 @@ class ButtonStyle(Enum):
|
|||||||
# Aliases
|
# Aliases
|
||||||
blurple = 1
|
blurple = 1
|
||||||
grey = 2
|
grey = 2
|
||||||
|
gray = 2
|
||||||
green = 3
|
green = 3
|
||||||
red = 4
|
red = 4
|
||||||
|
url = 5
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class StagePrivacyLevel(Enum):
|
class StagePrivacyLevel(Enum):
|
||||||
public = 1
|
public = 1
|
||||||
closed = 2
|
closed = 2
|
||||||
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
|
||||||
age_restricted = 3
|
age_restricted = 3
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
def create_unknown_value(cls: Type[T], val: Any) -> T:
|
def create_unknown_value(cls: Type[T], val: Any) -> T:
|
||||||
value_cls = cls._enum_value_cls_ # type: ignore
|
value_cls = cls._enum_value_cls_ # type: ignore
|
||||||
name = f'unknown_{val}'
|
name = f'unknown_{val}'
|
||||||
return value_cls(name=name, value=val)
|
return value_cls(name=name, value=val)
|
||||||
|
|
||||||
|
|
||||||
def try_enum(cls: Type[T], val: Any) -> T:
|
def try_enum(cls: Type[T], val: Any) -> T:
|
||||||
"""A function that tries to turn the value into enum ``cls``.
|
"""A function that tries to turn the value into enum ``cls``.
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,21 @@ 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 Dict, List, Optional, TYPE_CHECKING, Any, Tuple, Union
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from aiohttp import ClientResponse, ClientWebSocketResponse
|
||||||
|
|
||||||
|
try:
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
|
_ResponseType = Union[ClientResponse, Response]
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
_ResponseType = ClientResponse
|
||||||
|
|
||||||
|
from .interactions import Interaction
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'DiscordException',
|
'DiscordException',
|
||||||
'ClientException',
|
'ClientException',
|
||||||
@@ -36,44 +51,52 @@ __all__ = (
|
|||||||
'LoginFailure',
|
'LoginFailure',
|
||||||
'ConnectionClosed',
|
'ConnectionClosed',
|
||||||
'PrivilegedIntentsRequired',
|
'PrivilegedIntentsRequired',
|
||||||
|
'InteractionResponded',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DiscordException(Exception):
|
class DiscordException(Exception):
|
||||||
"""Base exception class for discord.py
|
"""Base exception class for discord.py
|
||||||
|
|
||||||
Ideally speaking, this could be caught to handle any exceptions thrown from this library.
|
Ideally speaking, this could be caught to handle any exceptions raised from this library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ClientException(DiscordException):
|
class ClientException(DiscordException):
|
||||||
"""Exception that's thrown when an operation in the :class:`Client` fails.
|
"""Exception that's raised when an operation in the :class:`Client` fails.
|
||||||
|
|
||||||
These are usually for exceptions that happened due to user input.
|
These are usually for exceptions that happened due to user input.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoMoreItems(DiscordException):
|
class NoMoreItems(DiscordException):
|
||||||
"""Exception that is thrown when an async iteration operation has no more
|
"""Exception that is raised when an async iteration operation has no more items."""
|
||||||
items."""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GatewayNotFound(DiscordException):
|
class GatewayNotFound(DiscordException):
|
||||||
"""An exception that is usually thrown when the gateway hub
|
"""An exception that is raised when the gateway for Discord could not be found"""
|
||||||
for the :class:`Client` websocket is not found."""
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
message = 'The gateway to connect to discord was not found.'
|
message = 'The gateway to connect to discord was not found.'
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
def flatten_error_dict(d, key=''):
|
|
||||||
items = []
|
def _flatten_error_dict(d: Dict[str, Any], key: str = '') -> Dict[str, str]:
|
||||||
|
items: List[Tuple[str, str]] = []
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
new_key = key + '.' + k if key else k
|
new_key = key + '.' + k if key else k
|
||||||
|
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
try:
|
try:
|
||||||
_errors = v['_errors']
|
_errors: List[Dict[str, Any]] = v['_errors']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
items.extend(flatten_error_dict(v, new_key).items())
|
items.extend(_flatten_error_dict(v, new_key).items())
|
||||||
else:
|
else:
|
||||||
items.append((new_key, ' '.join(x.get('message', '') for x in _errors)))
|
items.append((new_key, ' '.join(x.get('message', '') for x in _errors)))
|
||||||
else:
|
else:
|
||||||
@@ -81,8 +104,9 @@ def flatten_error_dict(d, key=''):
|
|||||||
|
|
||||||
return dict(items)
|
return dict(items)
|
||||||
|
|
||||||
|
|
||||||
class HTTPException(DiscordException):
|
class HTTPException(DiscordException):
|
||||||
"""Exception that's thrown when an HTTP request operation fails.
|
"""Exception that's raised when an HTTP request operation fails.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
------------
|
------------
|
||||||
@@ -99,21 +123,23 @@ class HTTPException(DiscordException):
|
|||||||
The Discord specific error code for the failure.
|
The Discord specific error code for the failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, response, message):
|
def __init__(self, response: _ResponseType, message: Optional[Union[str, Dict[str, Any]]]):
|
||||||
self.response = response
|
self.response: _ResponseType = response
|
||||||
self.status = response.status
|
self.status: int = response.status # type: ignore
|
||||||
|
self.code: int
|
||||||
|
self.text: str
|
||||||
if isinstance(message, dict):
|
if isinstance(message, dict):
|
||||||
self.code = message.get('code', 0)
|
self.code = message.get('code', 0)
|
||||||
base = message.get('message', '')
|
base = message.get('message', '')
|
||||||
errors = message.get('errors')
|
errors = message.get('errors')
|
||||||
if errors:
|
if errors:
|
||||||
errors = flatten_error_dict(errors)
|
errors = _flatten_error_dict(errors)
|
||||||
helpful = '\n'.join('In %s: %s' % t for t in errors.items())
|
helpful = '\n'.join('In %s: %s' % t for t in errors.items())
|
||||||
self.text = base + '\n' + helpful
|
self.text = base + '\n' + helpful
|
||||||
else:
|
else:
|
||||||
self.text = base
|
self.text = base
|
||||||
else:
|
else:
|
||||||
self.text = message
|
self.text = message or ''
|
||||||
self.code = 0
|
self.code = 0
|
||||||
|
|
||||||
fmt = '{0.status} {0.reason} (error code: {1})'
|
fmt = '{0.status} {0.reason} (error code: {1})'
|
||||||
@@ -122,54 +148,67 @@ class HTTPException(DiscordException):
|
|||||||
|
|
||||||
super().__init__(fmt.format(self.response, self.code, self.text))
|
super().__init__(fmt.format(self.response, self.code, self.text))
|
||||||
|
|
||||||
|
|
||||||
class Forbidden(HTTPException):
|
class Forbidden(HTTPException):
|
||||||
"""Exception that's thrown for when status code 403 occurs.
|
"""Exception that's raised for when status code 403 occurs.
|
||||||
|
|
||||||
Subclass of :exc:`HTTPException`
|
Subclass of :exc:`HTTPException`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NotFound(HTTPException):
|
class NotFound(HTTPException):
|
||||||
"""Exception that's thrown for when status code 404 occurs.
|
"""Exception that's raised for when status code 404 occurs.
|
||||||
|
|
||||||
Subclass of :exc:`HTTPException`
|
Subclass of :exc:`HTTPException`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DiscordServerError(HTTPException):
|
class DiscordServerError(HTTPException):
|
||||||
"""Exception that's thrown for when a 500 range status code occurs.
|
"""Exception that's raised for when a 500 range status code occurs.
|
||||||
|
|
||||||
Subclass of :exc:`HTTPException`.
|
Subclass of :exc:`HTTPException`.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidData(ClientException):
|
class InvalidData(ClientException):
|
||||||
"""Exception that's raised when the library encounters unknown
|
"""Exception that's raised when the library encounters unknown
|
||||||
or invalid data from Discord.
|
or invalid data from Discord.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidArgument(ClientException):
|
class InvalidArgument(ClientException):
|
||||||
"""Exception that's thrown when an argument to a function
|
"""Exception that's raised when an argument to a function
|
||||||
is invalid some way (e.g. wrong value or wrong type).
|
is invalid some way (e.g. wrong value or wrong type).
|
||||||
|
|
||||||
This could be considered the analogous of ``ValueError`` and
|
This could be considered the analogous of ``ValueError`` and
|
||||||
``TypeError`` except inherited from :exc:`ClientException` and thus
|
``TypeError`` except inherited from :exc:`ClientException` and thus
|
||||||
:exc:`DiscordException`.
|
:exc:`DiscordException`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LoginFailure(ClientException):
|
class LoginFailure(ClientException):
|
||||||
"""Exception that's thrown when the :meth:`Client.login` function
|
"""Exception that's raised when the :meth:`Client.login` function
|
||||||
fails to log you in from improper credentials or some other misc.
|
fails to log you in from improper credentials or some other misc.
|
||||||
failure.
|
failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConnectionClosed(ClientException):
|
class ConnectionClosed(ClientException):
|
||||||
"""Exception that's thrown when the gateway connection is
|
"""Exception that's raised when the gateway connection is
|
||||||
closed for reasons that could not be handled internally.
|
closed for reasons that could not be handled internally.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
@@ -181,17 +220,19 @@ class ConnectionClosed(ClientException):
|
|||||||
shard_id: Optional[:class:`int`]
|
shard_id: Optional[:class:`int`]
|
||||||
The shard ID that got closed if applicable.
|
The shard ID that got closed if applicable.
|
||||||
"""
|
"""
|
||||||
def __init__(self, socket, *, shard_id, code=None):
|
|
||||||
|
def __init__(self, socket: ClientWebSocketResponse, *, shard_id: Optional[int], code: Optional[int] = None):
|
||||||
# This exception is just the same exception except
|
# This exception is just the same exception except
|
||||||
# reconfigured to subclass ClientException for users
|
# reconfigured to subclass ClientException for users
|
||||||
self.code = code or socket.close_code
|
self.code: int = code or socket.close_code or -1
|
||||||
# aiohttp doesn't seem to consistently provide close reason
|
# aiohttp doesn't seem to consistently provide close reason
|
||||||
self.reason = ''
|
self.reason: str = ''
|
||||||
self.shard_id = shard_id
|
self.shard_id: Optional[int] = shard_id
|
||||||
super().__init__(f'Shard ID {self.shard_id} WebSocket closed with {self.code}')
|
super().__init__(f'Shard ID {self.shard_id} WebSocket closed with {self.code}')
|
||||||
|
|
||||||
|
|
||||||
class PrivilegedIntentsRequired(ClientException):
|
class PrivilegedIntentsRequired(ClientException):
|
||||||
"""Exception that's thrown when the gateway is requesting privileged intents
|
"""Exception that's raised when the gateway is requesting privileged intents
|
||||||
but they're not ticked in the developer page yet.
|
but they're not ticked in the developer page yet.
|
||||||
|
|
||||||
Go to https://discord.com/developers/applications/ and enable the intents
|
Go to https://discord.com/developers/applications/ and enable the intents
|
||||||
@@ -206,10 +247,31 @@ class PrivilegedIntentsRequired(ClientException):
|
|||||||
The shard ID that got closed if applicable.
|
The shard ID that got closed if applicable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, shard_id):
|
def __init__(self, shard_id: Optional[int]):
|
||||||
self.shard_id = shard_id
|
self.shard_id: Optional[int] = shard_id
|
||||||
msg = 'Shard ID %s is requesting privileged intents that have not been explicitly enabled in the ' \
|
msg = (
|
||||||
'developer portal. It is recommended to go to https://discord.com/developers/applications/ ' \
|
'Shard ID %s is requesting privileged intents that have not been explicitly enabled in the '
|
||||||
'and explicitly enable the privileged intents within your application\'s page. If this is not ' \
|
'developer portal. It is recommended to go to https://discord.com/developers/applications/ '
|
||||||
|
'and explicitly enable the privileged intents within your application\'s page. If this is not '
|
||||||
'possible, then consider disabling the privileged intents instead.'
|
'possible, then consider disabling the privileged intents instead.'
|
||||||
|
)
|
||||||
super().__init__(msg % shard_id)
|
super().__init__(msg % shard_id)
|
||||||
|
|
||||||
|
|
||||||
|
class InteractionResponded(ClientException):
|
||||||
|
"""Exception that's raised when sending another interaction response using
|
||||||
|
:class:`InteractionResponse` when one has already been done before.
|
||||||
|
|
||||||
|
An interaction can only respond once.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
interaction: :class:`Interaction`
|
||||||
|
The interaction that's already been responded to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, interaction: Interaction):
|
||||||
|
self.interaction: Interaction = interaction
|
||||||
|
super().__init__('This interaction has already been responded to before')
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,8 +573,8 @@ 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.
|
"""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
|
||||||
cog has registered will be removed as well.
|
cog has registered will be removed as well.
|
||||||
@@ -559,6 +585,11 @@ class BotBase(GroupMixin):
|
|||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The name of the cog to remove.
|
The name of the cog to remove.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[:class:`.Cog`]
|
||||||
|
The cog that was removed. ``None`` if not found.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cog = self.__cogs.pop(name, None)
|
cog = self.__cogs.pop(name, None)
|
||||||
@@ -570,14 +601,16 @@ class BotBase(GroupMixin):
|
|||||||
help_command.cog = None
|
help_command.cog = None
|
||||||
cog._eject(self)
|
cog._eject(self)
|
||||||
|
|
||||||
|
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():
|
||||||
@@ -601,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:
|
||||||
@@ -619,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
|
||||||
@@ -645,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
|
||||||
@@ -698,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
|
||||||
@@ -739,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
|
||||||
@@ -795,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
|
||||||
@@ -803,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')
|
||||||
@@ -830,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
|
||||||
@@ -868,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.
|
||||||
@@ -901,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)
|
||||||
@@ -938,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
|
||||||
@@ -968,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
|
||||||
|
|||||||
@@ -21,16 +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 copy
|
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.
|
||||||
|
|
||||||
@@ -90,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', {})
|
||||||
@@ -144,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
|
||||||
|
|
||||||
@@ -165,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.
|
||||||
@@ -174,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
|
||||||
@@ -187,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
|
||||||
--------
|
--------
|
||||||
@@ -210,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
|
||||||
@@ -238,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
|
||||||
@@ -249,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`.
|
||||||
@@ -272,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__
|
||||||
@@ -294,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
|
||||||
@@ -302,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
|
||||||
@@ -313,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.
|
||||||
|
|
||||||
@@ -323,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.
|
||||||
|
|
||||||
@@ -333,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.
|
||||||
|
|
||||||
@@ -343,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.
|
||||||
|
|
||||||
@@ -362,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`.
|
||||||
@@ -377,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`.
|
||||||
@@ -391,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
|
||||||
@@ -426,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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from .errors import *
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from discord.message import PartialMessageableChannel
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@@ -71,7 +72,9 @@ __all__ = (
|
|||||||
'CategoryChannelConverter',
|
'CategoryChannelConverter',
|
||||||
'IDConverter',
|
'IDConverter',
|
||||||
'StoreChannelConverter',
|
'StoreChannelConverter',
|
||||||
|
'ThreadConverter',
|
||||||
'GuildChannelConverter',
|
'GuildChannelConverter',
|
||||||
|
'GuildStickerConverter',
|
||||||
'clean_content',
|
'clean_content',
|
||||||
'Greedy',
|
'Greedy',
|
||||||
'run_converters',
|
'run_converters',
|
||||||
@@ -91,6 +94,7 @@ _utils_get = discord.utils.get
|
|||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
T_co = TypeVar('T_co', covariant=True)
|
T_co = TypeVar('T_co', covariant=True)
|
||||||
CT = TypeVar('CT', bound=discord.abc.GuildChannel)
|
CT = TypeVar('CT', bound=discord.abc.GuildChannel)
|
||||||
|
TT = TypeVar('TT', bound=discord.Thread)
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
@@ -325,22 +329,42 @@ class PartialMessageConverter(Converter[discord.PartialMessage]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_id_matches(argument):
|
def _get_id_matches(ctx, argument):
|
||||||
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,20})-)?(?P<message_id>[0-9]{15,20})$')
|
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,20})-)?(?P<message_id>[0-9]{15,20})$')
|
||||||
link_regex = re.compile(
|
link_regex = re.compile(
|
||||||
r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/'
|
r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/'
|
||||||
r'(?:[0-9]{15,20}|@me)'
|
r'(?P<guild_id>[0-9]{15,20}|@me)'
|
||||||
r'/(?P<channel_id>[0-9]{15,20})/(?P<message_id>[0-9]{15,20})/?$'
|
r'/(?P<channel_id>[0-9]{15,20})/(?P<message_id>[0-9]{15,20})/?$'
|
||||||
)
|
)
|
||||||
match = id_regex.match(argument) or link_regex.match(argument)
|
match = id_regex.match(argument) or link_regex.match(argument)
|
||||||
if not match:
|
if not match:
|
||||||
raise MessageNotFound(argument)
|
raise MessageNotFound(argument)
|
||||||
channel_id = match.group('channel_id')
|
data = match.groupdict()
|
||||||
return int(match.group('message_id')), int(channel_id) if channel_id else None
|
channel_id = discord.utils._get_as_snowflake(data, 'channel_id')
|
||||||
|
message_id = int(data['message_id'])
|
||||||
|
guild_id = data.get('guild_id')
|
||||||
|
if guild_id is None:
|
||||||
|
guild_id = ctx.guild and ctx.guild.id
|
||||||
|
elif guild_id == '@me':
|
||||||
|
guild_id = None
|
||||||
|
else:
|
||||||
|
guild_id = int(guild_id)
|
||||||
|
return guild_id, message_id, channel_id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_channel(ctx, guild_id, channel_id) -> Optional[PartialMessageableChannel]:
|
||||||
|
if guild_id is not None:
|
||||||
|
guild = ctx.bot.get_guild(guild_id)
|
||||||
|
if guild is not None and channel_id is not None:
|
||||||
|
return guild._resolve_channel(channel_id) # type: ignore
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.PartialMessage:
|
async def convert(self, ctx: Context, argument: str) -> discord.PartialMessage:
|
||||||
message_id, channel_id = self._get_id_matches(argument)
|
guild_id, message_id, channel_id = self._get_id_matches(ctx, argument)
|
||||||
channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
channel = self._resolve_channel(ctx, guild_id, channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
raise ChannelNotFound(channel_id)
|
raise ChannelNotFound(channel_id)
|
||||||
return discord.PartialMessage(channel=channel, id=message_id)
|
return discord.PartialMessage(channel=channel, id=message_id)
|
||||||
@@ -362,11 +386,11 @@ class MessageConverter(IDConverter[discord.Message]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.Message:
|
async def convert(self, ctx: Context, argument: str) -> discord.Message:
|
||||||
message_id, channel_id = PartialMessageConverter._get_id_matches(argument)
|
guild_id, message_id, channel_id = PartialMessageConverter._get_id_matches(ctx, argument)
|
||||||
message = ctx.bot._connection._get_message(message_id)
|
message = ctx.bot._connection._get_message(message_id)
|
||||||
if message:
|
if message:
|
||||||
return message
|
return message
|
||||||
channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
channel = PartialMessageConverter._resolve_channel(ctx, guild_id, channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
raise ChannelNotFound(channel_id)
|
raise ChannelNotFound(channel_id)
|
||||||
try:
|
try:
|
||||||
@@ -393,10 +417,10 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.abc.GuildChannel:
|
async def convert(self, ctx: Context, argument: str) -> discord.abc.GuildChannel:
|
||||||
return self._resolve_channel(ctx, argument, ctx.guild.channels, discord.abc.GuildChannel)
|
return self._resolve_channel(ctx, argument, 'channels', discord.abc.GuildChannel)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolve_channel(ctx: Context, argument: str, iterable: Iterable[CT], type: Type[CT]) -> CT:
|
def _resolve_channel(ctx: Context, argument: str, attribute: str, type: Type[CT]) -> CT:
|
||||||
bot = ctx.bot
|
bot = ctx.bot
|
||||||
|
|
||||||
match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument)
|
match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument)
|
||||||
@@ -406,6 +430,7 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]):
|
|||||||
if match is None:
|
if match is None:
|
||||||
# not a mention
|
# not a mention
|
||||||
if guild:
|
if guild:
|
||||||
|
iterable: Iterable[CT] = getattr(guild, attribute)
|
||||||
result: Optional[CT] = discord.utils.get(iterable, name=argument)
|
result: Optional[CT] = discord.utils.get(iterable, name=argument)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@@ -425,6 +450,29 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_thread(ctx: Context, argument: str, attribute: str, type: Type[TT]) -> TT:
|
||||||
|
bot = ctx.bot
|
||||||
|
|
||||||
|
match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument)
|
||||||
|
result = None
|
||||||
|
guild = ctx.guild
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
# not a mention
|
||||||
|
if guild:
|
||||||
|
iterable: Iterable[TT] = getattr(guild, attribute)
|
||||||
|
result: Optional[TT] = discord.utils.get(iterable, name=argument)
|
||||||
|
else:
|
||||||
|
thread_id = int(match.group(1))
|
||||||
|
if guild:
|
||||||
|
result = guild.get_thread(thread_id)
|
||||||
|
|
||||||
|
if not result or not isinstance(result, type):
|
||||||
|
raise ThreadNotFound(argument)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class TextChannelConverter(IDConverter[discord.TextChannel]):
|
class TextChannelConverter(IDConverter[discord.TextChannel]):
|
||||||
"""Converts to a :class:`~discord.TextChannel`.
|
"""Converts to a :class:`~discord.TextChannel`.
|
||||||
@@ -443,7 +491,7 @@ class TextChannelConverter(IDConverter[discord.TextChannel]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.TextChannel:
|
async def convert(self, ctx: Context, argument: str) -> discord.TextChannel:
|
||||||
return GuildChannelConverter._resolve_channel(ctx, argument, ctx.guild.text_channels, discord.TextChannel)
|
return GuildChannelConverter._resolve_channel(ctx, argument, 'text_channels', discord.TextChannel)
|
||||||
|
|
||||||
|
|
||||||
class VoiceChannelConverter(IDConverter[discord.VoiceChannel]):
|
class VoiceChannelConverter(IDConverter[discord.VoiceChannel]):
|
||||||
@@ -463,7 +511,7 @@ class VoiceChannelConverter(IDConverter[discord.VoiceChannel]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.VoiceChannel:
|
async def convert(self, ctx: Context, argument: str) -> discord.VoiceChannel:
|
||||||
return GuildChannelConverter._resolve_channel(ctx, argument, ctx.guild.voice_channels, discord.VoiceChannel)
|
return GuildChannelConverter._resolve_channel(ctx, argument, 'voice_channels', discord.VoiceChannel)
|
||||||
|
|
||||||
|
|
||||||
class StageChannelConverter(IDConverter[discord.StageChannel]):
|
class StageChannelConverter(IDConverter[discord.StageChannel]):
|
||||||
@@ -482,7 +530,7 @@ class StageChannelConverter(IDConverter[discord.StageChannel]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.StageChannel:
|
async def convert(self, ctx: Context, argument: str) -> discord.StageChannel:
|
||||||
return GuildChannelConverter._resolve_channel(ctx, argument, ctx.guild.stage_channels, discord.StageChannel)
|
return GuildChannelConverter._resolve_channel(ctx, argument, 'stage_channels', discord.StageChannel)
|
||||||
|
|
||||||
|
|
||||||
class CategoryChannelConverter(IDConverter[discord.CategoryChannel]):
|
class CategoryChannelConverter(IDConverter[discord.CategoryChannel]):
|
||||||
@@ -502,7 +550,7 @@ class CategoryChannelConverter(IDConverter[discord.CategoryChannel]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.CategoryChannel:
|
async def convert(self, ctx: Context, argument: str) -> discord.CategoryChannel:
|
||||||
return GuildChannelConverter._resolve_channel(ctx, argument, ctx.guild.categories, discord.CategoryChannel)
|
return GuildChannelConverter._resolve_channel(ctx, argument, 'categories', discord.CategoryChannel)
|
||||||
|
|
||||||
|
|
||||||
class StoreChannelConverter(IDConverter[discord.StoreChannel]):
|
class StoreChannelConverter(IDConverter[discord.StoreChannel]):
|
||||||
@@ -521,7 +569,25 @@ class StoreChannelConverter(IDConverter[discord.StoreChannel]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> discord.StoreChannel:
|
async def convert(self, ctx: Context, argument: str) -> discord.StoreChannel:
|
||||||
return GuildChannelConverter._resolve_channel(ctx, argument, ctx.guild.channels, discord.StoreChannel)
|
return GuildChannelConverter._resolve_channel(ctx, argument, 'channels', discord.StoreChannel)
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadConverter(IDConverter[discord.Thread]):
|
||||||
|
"""Coverts to a :class:`~discord.Thread`.
|
||||||
|
|
||||||
|
All lookups are via the local guild.
|
||||||
|
|
||||||
|
The lookup strategy is as follows (in order):
|
||||||
|
|
||||||
|
1. Lookup by ID.
|
||||||
|
2. Lookup by mention.
|
||||||
|
3. Lookup by name.
|
||||||
|
|
||||||
|
.. versionadded: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def convert(self, ctx: Context, argument: str) -> discord.Thread:
|
||||||
|
return GuildChannelConverter._resolve_thread(ctx, argument, 'threads', discord.Thread)
|
||||||
|
|
||||||
|
|
||||||
class ColourConverter(Converter[discord.Colour]):
|
class ColourConverter(Converter[discord.Colour]):
|
||||||
@@ -663,7 +729,7 @@ class InviteConverter(Converter[discord.Invite]):
|
|||||||
invite = await ctx.bot.fetch_invite(argument)
|
invite = await ctx.bot.fetch_invite(argument)
|
||||||
return invite
|
return invite
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise BadInviteArgument() from exc
|
raise BadInviteArgument(argument) from exc
|
||||||
|
|
||||||
|
|
||||||
class GuildConverter(IDConverter[discord.Guild]):
|
class GuildConverter(IDConverter[discord.Guild]):
|
||||||
@@ -726,11 +792,7 @@ class EmojiConverter(IDConverter[discord.Emoji]):
|
|||||||
emoji_id = int(match.group(1))
|
emoji_id = int(match.group(1))
|
||||||
|
|
||||||
# Try to look up emoji by id.
|
# Try to look up emoji by id.
|
||||||
if guild:
|
result = bot.get_emoji(emoji_id)
|
||||||
result = discord.utils.get(guild.emojis, id=emoji_id)
|
|
||||||
|
|
||||||
if result is None:
|
|
||||||
result = discord.utils.get(bot.emojis, id=emoji_id)
|
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
raise EmojiNotFound(argument)
|
raise EmojiNotFound(argument)
|
||||||
@@ -762,6 +824,45 @@ class PartialEmojiConverter(Converter[discord.PartialEmoji]):
|
|||||||
raise PartialEmojiConversionFailure(argument)
|
raise PartialEmojiConversionFailure(argument)
|
||||||
|
|
||||||
|
|
||||||
|
class GuildStickerConverter(IDConverter[discord.GuildSticker]):
|
||||||
|
"""Converts to a :class:`~discord.GuildSticker`.
|
||||||
|
|
||||||
|
All lookups are done for the local guild first, if available. If that lookup
|
||||||
|
fails, then it checks the client's global cache.
|
||||||
|
|
||||||
|
The lookup strategy is as follows (in order):
|
||||||
|
|
||||||
|
1. Lookup by ID.
|
||||||
|
3. Lookup by name
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def convert(self, ctx: Context, argument: str) -> discord.GuildSticker:
|
||||||
|
match = self._get_id_match(argument)
|
||||||
|
result = None
|
||||||
|
bot = ctx.bot
|
||||||
|
guild = ctx.guild
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
# Try to get the sticker by name. Try local guild first.
|
||||||
|
if guild:
|
||||||
|
result = discord.utils.get(guild.stickers, name=argument)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
result = discord.utils.get(bot.stickers, name=argument)
|
||||||
|
else:
|
||||||
|
sticker_id = int(match.group(1))
|
||||||
|
|
||||||
|
# Try to look up sticker by id.
|
||||||
|
result = bot.get_sticker(sticker_id)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
raise GuildStickerNotFound(argument)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class clean_content(Converter[str]):
|
class clean_content(Converter[str]):
|
||||||
"""Converts the argument to mention scrubbed version of
|
"""Converts the argument to mention scrubbed version of
|
||||||
said content.
|
said content.
|
||||||
@@ -782,67 +883,66 @@ class clean_content(Converter[str]):
|
|||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, fix_channel_mentions: bool = False, use_nicknames: bool = True, escape_markdown: bool = False, remove_markdown: bool = False) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
fix_channel_mentions: bool = False,
|
||||||
|
use_nicknames: bool = True,
|
||||||
|
escape_markdown: bool = False,
|
||||||
|
remove_markdown: bool = False,
|
||||||
|
) -> None:
|
||||||
self.fix_channel_mentions = fix_channel_mentions
|
self.fix_channel_mentions = fix_channel_mentions
|
||||||
self.use_nicknames = use_nicknames
|
self.use_nicknames = use_nicknames
|
||||||
self.escape_markdown = escape_markdown
|
self.escape_markdown = escape_markdown
|
||||||
self.remove_markdown = remove_markdown
|
self.remove_markdown = remove_markdown
|
||||||
|
|
||||||
async def convert(self, ctx: Context, argument: str) -> str:
|
async def convert(self, ctx: Context, argument: str) -> str:
|
||||||
message = ctx.message
|
msg = ctx.message
|
||||||
transformations = {}
|
|
||||||
|
|
||||||
if self.fix_channel_mentions and ctx.guild:
|
|
||||||
|
|
||||||
def resolve_channel(id, *, _get=ctx.guild.get_channel):
|
|
||||||
ch = _get(id)
|
|
||||||
return f'<#{id}>', ('#' + ch.name if ch else '#deleted-channel')
|
|
||||||
|
|
||||||
transformations.update(resolve_channel(channel) for channel in message.raw_channel_mentions)
|
|
||||||
|
|
||||||
if self.use_nicknames and ctx.guild:
|
|
||||||
|
|
||||||
def resolve_member(id, *, _get=ctx.guild.get_member):
|
|
||||||
m = _get(id)
|
|
||||||
return '@' + m.display_name if m else '@deleted-user'
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def resolve_member(id, *, _get=ctx.bot.get_user):
|
|
||||||
m = _get(id)
|
|
||||||
return '@' + m.name if m else '@deleted-user'
|
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
transformations.update(
|
|
||||||
(f'<@{member_id}>', resolve_member(member_id))
|
|
||||||
for member_id in message.raw_mentions
|
|
||||||
)
|
|
||||||
|
|
||||||
transformations.update(
|
|
||||||
(f'<@!{member_id}>', resolve_member(member_id))
|
|
||||||
for member_id in message.raw_mentions
|
|
||||||
)
|
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
if ctx.guild:
|
if ctx.guild:
|
||||||
|
|
||||||
def resolve_role(_id, *, _find=ctx.guild.get_role):
|
def resolve_member(id: int) -> str:
|
||||||
r = _find(_id)
|
m = _utils_get(msg.mentions, id=id) or ctx.guild.get_member(id)
|
||||||
return '@' + r.name if r else '@deleted-role'
|
return f'@{m.display_name if self.use_nicknames else m.name}' if m else '@deleted-user'
|
||||||
|
|
||||||
# fmt: off
|
def resolve_role(id: int) -> str:
|
||||||
transformations.update(
|
r = _utils_get(msg.role_mentions, id=id) or ctx.guild.get_role(id)
|
||||||
(f'<@&{role_id}>', resolve_role(role_id))
|
return f'@{r.name}' if r else '@deleted-role'
|
||||||
for role_id in message.raw_role_mentions
|
|
||||||
)
|
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
def repl(obj):
|
else:
|
||||||
return transformations.get(obj.group(0), '')
|
|
||||||
|
|
||||||
pattern = re.compile('|'.join(transformations.keys()))
|
def resolve_member(id: int) -> str:
|
||||||
result = pattern.sub(repl, argument)
|
m = _utils_get(msg.mentions, id=id) or ctx.bot.get_user(id)
|
||||||
|
return f'@{m.name}' if m else '@deleted-user'
|
||||||
|
|
||||||
|
def resolve_role(id: int) -> str:
|
||||||
|
return '@deleted-role'
|
||||||
|
|
||||||
|
if self.fix_channel_mentions and ctx.guild:
|
||||||
|
|
||||||
|
def resolve_channel(id: int) -> str:
|
||||||
|
c = ctx.guild.get_channel(id)
|
||||||
|
return f'#{c.name}' if c else '#deleted-channel'
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def resolve_channel(id: int) -> str:
|
||||||
|
return f'<#{id}>'
|
||||||
|
|
||||||
|
transforms = {
|
||||||
|
'@': resolve_member,
|
||||||
|
'@!': resolve_member,
|
||||||
|
'#': resolve_channel,
|
||||||
|
'@&': resolve_role,
|
||||||
|
}
|
||||||
|
|
||||||
|
def repl(match: re.Match) -> str:
|
||||||
|
type = match[1]
|
||||||
|
id = int(match[2])
|
||||||
|
transformed = transforms[type](id)
|
||||||
|
return transformed
|
||||||
|
|
||||||
|
result = re.sub(r'<(@[!&]?|#)([0-9]{15,20})>', repl, argument)
|
||||||
if self.escape_markdown:
|
if self.escape_markdown:
|
||||||
result = discord.utils.escape_markdown(result)
|
result = discord.utils.escape_markdown(result)
|
||||||
elif self.remove_markdown:
|
elif self.remove_markdown:
|
||||||
@@ -950,7 +1050,9 @@ CONVERTER_MAPPING: Dict[Type[Any], Any] = {
|
|||||||
discord.PartialEmoji: PartialEmojiConverter,
|
discord.PartialEmoji: PartialEmojiConverter,
|
||||||
discord.CategoryChannel: CategoryChannelConverter,
|
discord.CategoryChannel: CategoryChannelConverter,
|
||||||
discord.StoreChannel: StoreChannelConverter,
|
discord.StoreChannel: StoreChannelConverter,
|
||||||
|
discord.Thread: ThreadConverter,
|
||||||
discord.abc.GuildChannel: GuildChannelConverter,
|
discord.abc.GuildChannel: GuildChannelConverter,
|
||||||
|
discord.GuildSticker: GuildStickerConverter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 Any, Callable, Deque, Dict, Optional, Type, TypeVar, TYPE_CHECKING
|
||||||
from discord.enums import Enum
|
from discord.enums import Enum
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -30,6 +34,9 @@ from collections import deque
|
|||||||
from ...abc import PrivateChannel
|
from ...abc import PrivateChannel
|
||||||
from .errors import MaxConcurrencyReached
|
from .errors import MaxConcurrencyReached
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...message import Message
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BucketType',
|
'BucketType',
|
||||||
'Cooldown',
|
'Cooldown',
|
||||||
@@ -38,6 +45,9 @@ __all__ = (
|
|||||||
'MaxConcurrency',
|
'MaxConcurrency',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
C = TypeVar('C', bound='CooldownMapping')
|
||||||
|
MC = TypeVar('MC', bound='MaxConcurrency')
|
||||||
|
|
||||||
class BucketType(Enum):
|
class BucketType(Enum):
|
||||||
default = 0
|
default = 0
|
||||||
user = 1
|
user = 1
|
||||||
@@ -47,7 +57,7 @@ class BucketType(Enum):
|
|||||||
category = 5
|
category = 5
|
||||||
role = 6
|
role = 6
|
||||||
|
|
||||||
def get_key(self, msg):
|
def get_key(self, msg: Message) -> Any:
|
||||||
if self is BucketType.user:
|
if self is BucketType.user:
|
||||||
return msg.author.id
|
return msg.author.id
|
||||||
elif self is BucketType.guild:
|
elif self is BucketType.guild:
|
||||||
@@ -57,29 +67,52 @@ class BucketType(Enum):
|
|||||||
elif self is BucketType.member:
|
elif self is BucketType.member:
|
||||||
return ((msg.guild and msg.guild.id), msg.author.id)
|
return ((msg.guild and msg.guild.id), msg.author.id)
|
||||||
elif self is BucketType.category:
|
elif self is BucketType.category:
|
||||||
return (msg.channel.category or msg.channel).id
|
return (msg.channel.category or msg.channel).id # type: ignore
|
||||||
elif self is BucketType.role:
|
elif self is BucketType.role:
|
||||||
# we return the channel id of a private-channel as there are only roles in guilds
|
# we return the channel id of a private-channel as there are only roles in guilds
|
||||||
# and that yields the same result as for a guild with only the @everyone role
|
# and that yields the same result as for a guild with only the @everyone role
|
||||||
# NOTE: PrivateChannel doesn't actually have an id attribute but we assume we are
|
# NOTE: PrivateChannel doesn't actually have an id attribute but we assume we are
|
||||||
# recieving a DMChannel or GroupChannel which inherit from PrivateChannel and do
|
# recieving a DMChannel or GroupChannel which inherit from PrivateChannel and do
|
||||||
return (msg.channel if isinstance(msg.channel, PrivateChannel) else msg.author.top_role).id
|
return (msg.channel if isinstance(msg.channel, PrivateChannel) else msg.author.top_role).id # type: ignore
|
||||||
|
|
||||||
def __call__(self, msg):
|
def __call__(self, msg: Message) -> Any:
|
||||||
return self.get_key(msg)
|
return self.get_key(msg)
|
||||||
|
|
||||||
|
|
||||||
class Cooldown:
|
class Cooldown:
|
||||||
|
"""Represents a cooldown for a command.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
rate: :class:`int`
|
||||||
|
The total number of tokens available per :attr:`per` seconds.
|
||||||
|
per: :class:`float`
|
||||||
|
The length of the cooldown period in seconds.
|
||||||
|
"""
|
||||||
|
|
||||||
__slots__ = ('rate', 'per', '_window', '_tokens', '_last')
|
__slots__ = ('rate', 'per', '_window', '_tokens', '_last')
|
||||||
|
|
||||||
def __init__(self, rate, per):
|
def __init__(self, rate: float, per: float) -> None:
|
||||||
self.rate = int(rate)
|
self.rate: int = int(rate)
|
||||||
self.per = float(per)
|
self.per: float = float(per)
|
||||||
self._window = 0.0
|
self._window: float = 0.0
|
||||||
self._tokens = self.rate
|
self._tokens: int = self.rate
|
||||||
self._last = 0.0
|
self._last: float = 0.0
|
||||||
|
|
||||||
def get_tokens(self, current=None):
|
def get_tokens(self, current: Optional[float] = None) -> int:
|
||||||
|
"""Returns the number of available tokens before rate limiting is applied.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
current: Optional[:class:`float`]
|
||||||
|
The time in seconds since Unix epoch to calculate tokens at.
|
||||||
|
If not supplied then :func:`time.time()` is used.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`int`
|
||||||
|
The number of tokens available before the cooldown is to be applied.
|
||||||
|
"""
|
||||||
if not current:
|
if not current:
|
||||||
current = time.time()
|
current = time.time()
|
||||||
|
|
||||||
@@ -89,7 +122,20 @@ class Cooldown:
|
|||||||
tokens = self.rate
|
tokens = self.rate
|
||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
def get_retry_after(self, current=None):
|
def get_retry_after(self, current: Optional[float] = None) -> float:
|
||||||
|
"""Returns the time in seconds until the cooldown will be reset.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-------------
|
||||||
|
current: Optional[:class:`float`]
|
||||||
|
The current time in seconds since Unix epoch.
|
||||||
|
If not supplied, then :func:`time.time()` is used.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`float`
|
||||||
|
The number of seconds to wait before this cooldown will be reset.
|
||||||
|
"""
|
||||||
current = current or time.time()
|
current = current or time.time()
|
||||||
tokens = self.get_tokens(current)
|
tokens = self.get_tokens(current)
|
||||||
|
|
||||||
@@ -98,7 +144,20 @@ class Cooldown:
|
|||||||
|
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
def update_rate_limit(self, current=None):
|
def update_rate_limit(self, current: Optional[float] = None) -> Optional[float]:
|
||||||
|
"""Updates the cooldown rate limit.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-------------
|
||||||
|
current: Optional[:class:`float`]
|
||||||
|
The time in seconds since Unix epoch to update the rate limit at.
|
||||||
|
If not supplied, then :func:`time.time()` is used.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[:class:`float`]
|
||||||
|
The retry-after time in seconds if rate limited.
|
||||||
|
"""
|
||||||
current = current or time.time()
|
current = current or time.time()
|
||||||
self._last = current
|
self._last = current
|
||||||
|
|
||||||
@@ -115,47 +174,58 @@ class Cooldown:
|
|||||||
# we're not so decrement our tokens
|
# we're not so decrement our tokens
|
||||||
self._tokens -= 1
|
self._tokens -= 1
|
||||||
|
|
||||||
# see if we got rate limited due to this token change, and if
|
def reset(self) -> None:
|
||||||
# so update the window to point to our current time frame
|
"""Reset the cooldown to its initial state."""
|
||||||
if self._tokens == 0:
|
|
||||||
self._window = current
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self._tokens = self.rate
|
self._tokens = self.rate
|
||||||
self._last = 0.0
|
self._last = 0.0
|
||||||
|
|
||||||
def copy(self):
|
def copy(self) -> Cooldown:
|
||||||
|
"""Creates a copy of this cooldown.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Cooldown`
|
||||||
|
A new instance of this cooldown.
|
||||||
|
"""
|
||||||
return Cooldown(self.rate, self.per)
|
return Cooldown(self.rate, self.per)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<Cooldown rate: {self.rate} per: {self.per} window: {self._window} tokens: {self._tokens}>'
|
return f'<Cooldown rate: {self.rate} per: {self.per} window: {self._window} tokens: {self._tokens}>'
|
||||||
|
|
||||||
class CooldownMapping:
|
class CooldownMapping:
|
||||||
def __init__(self, original, type):
|
def __init__(
|
||||||
|
self,
|
||||||
|
original: Optional[Cooldown],
|
||||||
|
type: Callable[[Message], Any],
|
||||||
|
) -> None:
|
||||||
if not callable(type):
|
if not callable(type):
|
||||||
raise TypeError('Cooldown type must be a BucketType or callable')
|
raise TypeError('Cooldown type must be a BucketType or callable')
|
||||||
|
|
||||||
self._cache = {}
|
self._cache: Dict[Any, Cooldown] = {}
|
||||||
self._cooldown = original
|
self._cooldown: Optional[Cooldown] = original
|
||||||
self._type = type
|
self._type: Callable[[Message], Any] = type
|
||||||
|
|
||||||
def copy(self):
|
def copy(self) -> CooldownMapping:
|
||||||
ret = CooldownMapping(self._cooldown, self._type)
|
ret = CooldownMapping(self._cooldown, self._type)
|
||||||
ret._cache = self._cache.copy()
|
ret._cache = self._cache.copy()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self) -> bool:
|
||||||
return self._cooldown is not None
|
return self._cooldown is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self) -> Callable[[Message], Any]:
|
||||||
|
return self._type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_cooldown(cls, rate, per, type):
|
def from_cooldown(cls: Type[C], rate, per, type) -> C:
|
||||||
return cls(Cooldown(rate, per), type)
|
return cls(Cooldown(rate, per), type)
|
||||||
|
|
||||||
def _bucket_key(self, msg):
|
def _bucket_key(self, msg: Message) -> Any:
|
||||||
return self._type(msg)
|
return self._type(msg)
|
||||||
|
|
||||||
def _verify_cache_integrity(self, current=None):
|
def _verify_cache_integrity(self, current: Optional[float] = None) -> None:
|
||||||
# we want to delete all cache objects that haven't been used
|
# we want to delete all cache objects that haven't been used
|
||||||
# in a cooldown window. e.g. if we have a command that has a
|
# in a cooldown window. e.g. if we have a command that has a
|
||||||
# cooldown of 60s and it has not been used in 60s then that key should be deleted
|
# cooldown of 60s and it has not been used in 60s then that key should be deleted
|
||||||
@@ -164,12 +234,12 @@ class CooldownMapping:
|
|||||||
for k in dead_keys:
|
for k in dead_keys:
|
||||||
del self._cache[k]
|
del self._cache[k]
|
||||||
|
|
||||||
def create_bucket(self, message):
|
def create_bucket(self, message: Message) -> Cooldown:
|
||||||
return self._cooldown.copy()
|
return self._cooldown.copy() # type: ignore
|
||||||
|
|
||||||
def get_bucket(self, message, current=None):
|
def get_bucket(self, message: Message, current: Optional[float] = None) -> Cooldown:
|
||||||
if self._type is BucketType.default:
|
if self._type is BucketType.default:
|
||||||
return self._cooldown
|
return self._cooldown # type: ignore
|
||||||
|
|
||||||
self._verify_cache_integrity(current)
|
self._verify_cache_integrity(current)
|
||||||
key = self._bucket_key(message)
|
key = self._bucket_key(message)
|
||||||
@@ -182,26 +252,30 @@ class CooldownMapping:
|
|||||||
|
|
||||||
return bucket
|
return bucket
|
||||||
|
|
||||||
def update_rate_limit(self, message, current=None):
|
def update_rate_limit(self, message: Message, current: Optional[float] = None) -> Optional[float]:
|
||||||
bucket = self.get_bucket(message, current)
|
bucket = self.get_bucket(message, current)
|
||||||
return bucket.update_rate_limit(current)
|
return bucket.update_rate_limit(current)
|
||||||
|
|
||||||
class DynamicCooldownMapping(CooldownMapping):
|
class DynamicCooldownMapping(CooldownMapping):
|
||||||
|
|
||||||
def __init__(self, factory, type):
|
def __init__(
|
||||||
|
self,
|
||||||
|
factory: Callable[[Message], Cooldown],
|
||||||
|
type: Callable[[Message], Any]
|
||||||
|
) -> None:
|
||||||
super().__init__(None, type)
|
super().__init__(None, type)
|
||||||
self._factory = factory
|
self._factory: Callable[[Message], Cooldown] = factory
|
||||||
|
|
||||||
def copy(self):
|
def copy(self) -> DynamicCooldownMapping:
|
||||||
ret = DynamicCooldownMapping(self._factory, self._type)
|
ret = DynamicCooldownMapping(self._factory, self._type)
|
||||||
ret._cache = self._cache.copy()
|
ret._cache = self._cache.copy()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_bucket(self, message):
|
def create_bucket(self, message: Message) -> Cooldown:
|
||||||
return self._factory(message)
|
return self._factory(message)
|
||||||
|
|
||||||
class _Semaphore:
|
class _Semaphore:
|
||||||
@@ -219,28 +293,28 @@ class _Semaphore:
|
|||||||
|
|
||||||
__slots__ = ('value', 'loop', '_waiters')
|
__slots__ = ('value', 'loop', '_waiters')
|
||||||
|
|
||||||
def __init__(self, number):
|
def __init__(self, number: int) -> None:
|
||||||
self.value = number
|
self.value: int = number
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
|
||||||
self._waiters = deque()
|
self._waiters: Deque[asyncio.Future] = deque()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<_Semaphore value={self.value} waiters={len(self._waiters)}>'
|
return f'<_Semaphore value={self.value} waiters={len(self._waiters)}>'
|
||||||
|
|
||||||
def locked(self):
|
def locked(self) -> bool:
|
||||||
return self.value == 0
|
return self.value == 0
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self) -> bool:
|
||||||
return len(self._waiters) > 0
|
return len(self._waiters) > 0
|
||||||
|
|
||||||
def wake_up(self):
|
def wake_up(self) -> None:
|
||||||
while self._waiters:
|
while self._waiters:
|
||||||
future = self._waiters.popleft()
|
future = self._waiters.popleft()
|
||||||
if not future.done():
|
if not future.done():
|
||||||
future.set_result(None)
|
future.set_result(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
async def acquire(self, *, wait=False):
|
async def acquire(self, *, wait: bool = False) -> bool:
|
||||||
if not wait and self.value <= 0:
|
if not wait and self.value <= 0:
|
||||||
# signal that we're not acquiring
|
# signal that we're not acquiring
|
||||||
return False
|
return False
|
||||||
@@ -259,18 +333,18 @@ class _Semaphore:
|
|||||||
self.value -= 1
|
self.value -= 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def release(self):
|
def release(self) -> None:
|
||||||
self.value += 1
|
self.value += 1
|
||||||
self.wake_up()
|
self.wake_up()
|
||||||
|
|
||||||
class MaxConcurrency:
|
class MaxConcurrency:
|
||||||
__slots__ = ('number', 'per', 'wait', '_mapping')
|
__slots__ = ('number', 'per', 'wait', '_mapping')
|
||||||
|
|
||||||
def __init__(self, number, *, per, wait):
|
def __init__(self, number: int, *, per: BucketType, wait: bool) -> None:
|
||||||
self._mapping = {}
|
self._mapping: Dict[Any, _Semaphore] = {}
|
||||||
self.per = per
|
self.per: BucketType = per
|
||||||
self.number = number
|
self.number: int = number
|
||||||
self.wait = wait
|
self.wait: bool = wait
|
||||||
|
|
||||||
if number <= 0:
|
if number <= 0:
|
||||||
raise ValueError('max_concurrency \'number\' cannot be less than 1')
|
raise ValueError('max_concurrency \'number\' cannot be less than 1')
|
||||||
@@ -278,16 +352,16 @@ class MaxConcurrency:
|
|||||||
if not isinstance(per, BucketType):
|
if not isinstance(per, BucketType):
|
||||||
raise TypeError(f'max_concurrency \'per\' must be of type BucketType not {type(per)!r}')
|
raise TypeError(f'max_concurrency \'per\' must be of type BucketType not {type(per)!r}')
|
||||||
|
|
||||||
def copy(self):
|
def copy(self: MC) -> MC:
|
||||||
return self.__class__(self.number, per=self.per, wait=self.wait)
|
return self.__class__(self.number, per=self.per, wait=self.wait)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<MaxConcurrency per={self.per!r} number={self.number} wait={self.wait}>'
|
return f'<MaxConcurrency per={self.per!r} number={self.number} wait={self.wait}>'
|
||||||
|
|
||||||
def get_key(self, message):
|
def get_key(self, message: Message) -> Any:
|
||||||
return self.per.get_key(message)
|
return self.per.get_key(message)
|
||||||
|
|
||||||
async def acquire(self, message):
|
async def acquire(self, message: Message) -> None:
|
||||||
key = self.get_key(message)
|
key = self.get_key(message)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -299,7 +373,7 @@ class MaxConcurrency:
|
|||||||
if not acquired:
|
if not acquired:
|
||||||
raise MaxConcurrencyReached(self.number, self.per)
|
raise MaxConcurrencyReached(self.number, self.per)
|
||||||
|
|
||||||
async def release(self, message):
|
async def release(self, message: Message) -> None:
|
||||||
# Technically there's no reason for this function to be async
|
# Technically there's no reason for this function to be async
|
||||||
# But it might be more useful in the future
|
# But it might be more useful in the future
|
||||||
key = self.get_key(message)
|
key = self.get_key(message)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,8 +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
|
||||||
|
|
||||||
|
from typing import Optional, Any, TYPE_CHECKING, List, Callable, Type, Tuple, Union
|
||||||
|
|
||||||
from discord.errors import ClientException, DiscordException
|
from discord.errors import ClientException, DiscordException
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from inspect import Parameter
|
||||||
|
|
||||||
|
from .converter import Converter
|
||||||
|
from .context import Context
|
||||||
|
from .cooldowns import Cooldown, BucketType
|
||||||
|
from .flags import Flag
|
||||||
|
from discord.abc import GuildChannel
|
||||||
|
from discord.threads import Thread
|
||||||
|
from discord.types.snowflake import Snowflake, SnowflakeList
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CommandError',
|
'CommandError',
|
||||||
@@ -47,12 +62,14 @@ __all__ = (
|
|||||||
'GuildNotFound',
|
'GuildNotFound',
|
||||||
'UserNotFound',
|
'UserNotFound',
|
||||||
'ChannelNotFound',
|
'ChannelNotFound',
|
||||||
|
'ThreadNotFound',
|
||||||
'ChannelNotReadable',
|
'ChannelNotReadable',
|
||||||
'BadColourArgument',
|
'BadColourArgument',
|
||||||
'BadColorArgument',
|
'BadColorArgument',
|
||||||
'RoleNotFound',
|
'RoleNotFound',
|
||||||
'BadInviteArgument',
|
'BadInviteArgument',
|
||||||
'EmojiNotFound',
|
'EmojiNotFound',
|
||||||
|
'GuildStickerNotFound',
|
||||||
'PartialEmojiConversionFailure',
|
'PartialEmojiConversionFailure',
|
||||||
'BadBoolArgument',
|
'BadBoolArgument',
|
||||||
'MissingRole',
|
'MissingRole',
|
||||||
@@ -92,7 +109,7 @@ class CommandError(DiscordException):
|
|||||||
in a special way as they are caught and passed into a special event
|
in a special way as they are caught and passed into a special event
|
||||||
from :class:`.Bot`\, :func:`.on_command_error`.
|
from :class:`.Bot`\, :func:`.on_command_error`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message=None, *args):
|
def __init__(self, message: Optional[str] = None, *args: Any) -> None:
|
||||||
if message is not None:
|
if message is not None:
|
||||||
# clean-up @everyone and @here mentions
|
# clean-up @everyone and @here mentions
|
||||||
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
|
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
|
||||||
@@ -113,9 +130,9 @@ class ConversionError(CommandError):
|
|||||||
The original exception that was raised. You can also get this via
|
The original exception that was raised. You can also get this via
|
||||||
the ``__cause__`` attribute.
|
the ``__cause__`` attribute.
|
||||||
"""
|
"""
|
||||||
def __init__(self, converter, original):
|
def __init__(self, converter: Converter, original: Exception) -> None:
|
||||||
self.converter = converter
|
self.converter: Converter = converter
|
||||||
self.original = original
|
self.original: Exception = original
|
||||||
|
|
||||||
class UserInputError(CommandError):
|
class UserInputError(CommandError):
|
||||||
"""The base exception type for errors that involve errors
|
"""The base exception type for errors that involve errors
|
||||||
@@ -147,8 +164,8 @@ class MissingRequiredArgument(UserInputError):
|
|||||||
param: :class:`inspect.Parameter`
|
param: :class:`inspect.Parameter`
|
||||||
The argument that is missing.
|
The argument that is missing.
|
||||||
"""
|
"""
|
||||||
def __init__(self, param):
|
def __init__(self, param: Parameter) -> None:
|
||||||
self.param = param
|
self.param: Parameter = param
|
||||||
super().__init__(f'{param.name} is a required argument that is missing.')
|
super().__init__(f'{param.name} is a required argument that is missing.')
|
||||||
|
|
||||||
class TooManyArguments(UserInputError):
|
class TooManyArguments(UserInputError):
|
||||||
@@ -189,9 +206,9 @@ class CheckAnyFailure(CheckFailure):
|
|||||||
A list of check predicates that failed.
|
A list of check predicates that failed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, checks, errors):
|
def __init__(self, checks: List[CheckFailure], errors: List[Callable[[Context], bool]]) -> None:
|
||||||
self.checks = checks
|
self.checks: List[CheckFailure] = checks
|
||||||
self.errors = errors
|
self.errors: List[Callable[[Context], bool]] = errors
|
||||||
super().__init__('You do not have permission to run this command.')
|
super().__init__('You do not have permission to run this command.')
|
||||||
|
|
||||||
class PrivateMessageOnly(CheckFailure):
|
class PrivateMessageOnly(CheckFailure):
|
||||||
@@ -200,7 +217,7 @@ class PrivateMessageOnly(CheckFailure):
|
|||||||
|
|
||||||
This inherits from :exc:`CheckFailure`
|
This inherits from :exc:`CheckFailure`
|
||||||
"""
|
"""
|
||||||
def __init__(self, message=None):
|
def __init__(self, message: Optional[str] = None) -> None:
|
||||||
super().__init__(message or 'This command can only be used in private messages.')
|
super().__init__(message or 'This command can only be used in private messages.')
|
||||||
|
|
||||||
class NoPrivateMessage(CheckFailure):
|
class NoPrivateMessage(CheckFailure):
|
||||||
@@ -210,7 +227,7 @@ class NoPrivateMessage(CheckFailure):
|
|||||||
This inherits from :exc:`CheckFailure`
|
This inherits from :exc:`CheckFailure`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message=None):
|
def __init__(self, message: Optional[str] = None) -> None:
|
||||||
super().__init__(message or 'This command cannot be used in private messages.')
|
super().__init__(message or 'This command cannot be used in private messages.')
|
||||||
|
|
||||||
class NotOwner(CheckFailure):
|
class NotOwner(CheckFailure):
|
||||||
@@ -233,8 +250,8 @@ class ObjectNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The argument supplied by the caller that was not matched
|
The argument supplied by the caller that was not matched
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'{argument!r} does not follow a valid ID or mention format.')
|
super().__init__(f'{argument!r} does not follow a valid ID or mention format.')
|
||||||
|
|
||||||
class MemberNotFound(BadArgument):
|
class MemberNotFound(BadArgument):
|
||||||
@@ -250,8 +267,8 @@ class MemberNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The member supplied by the caller that was not found
|
The member supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Member "{argument}" not found.')
|
super().__init__(f'Member "{argument}" not found.')
|
||||||
|
|
||||||
class GuildNotFound(BadArgument):
|
class GuildNotFound(BadArgument):
|
||||||
@@ -266,8 +283,8 @@ class GuildNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The guild supplied by the called that was not found
|
The guild supplied by the called that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Guild "{argument}" not found.')
|
super().__init__(f'Guild "{argument}" not found.')
|
||||||
|
|
||||||
class UserNotFound(BadArgument):
|
class UserNotFound(BadArgument):
|
||||||
@@ -283,8 +300,8 @@ class UserNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The user supplied by the caller that was not found
|
The user supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'User "{argument}" not found.')
|
super().__init__(f'User "{argument}" not found.')
|
||||||
|
|
||||||
class MessageNotFound(BadArgument):
|
class MessageNotFound(BadArgument):
|
||||||
@@ -299,8 +316,8 @@ class MessageNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The message supplied by the caller that was not found
|
The message supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Message "{argument}" not found.')
|
super().__init__(f'Message "{argument}" not found.')
|
||||||
|
|
||||||
class ChannelNotReadable(BadArgument):
|
class ChannelNotReadable(BadArgument):
|
||||||
@@ -313,11 +330,11 @@ class ChannelNotReadable(BadArgument):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
argument: :class:`.abc.GuildChannel`
|
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):
|
def __init__(self, argument: Union[GuildChannel, Thread]) -> None:
|
||||||
self.argument = argument
|
self.argument: Union[GuildChannel, Thread] = argument
|
||||||
super().__init__(f"Can't read messages in {argument.mention}.")
|
super().__init__(f"Can't read messages in {argument.mention}.")
|
||||||
|
|
||||||
class ChannelNotFound(BadArgument):
|
class ChannelNotFound(BadArgument):
|
||||||
@@ -332,10 +349,26 @@ class ChannelNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The channel supplied by the caller that was not found
|
The channel supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Channel "{argument}" not found.')
|
super().__init__(f'Channel "{argument}" not found.')
|
||||||
|
|
||||||
|
class ThreadNotFound(BadArgument):
|
||||||
|
"""Exception raised when the bot can not find the thread.
|
||||||
|
|
||||||
|
This inherits from :exc:`BadArgument`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
argument: :class:`str`
|
||||||
|
The thread supplied by the caller that was not found
|
||||||
|
"""
|
||||||
|
def __init__(self, argument: str) -> None:
|
||||||
|
self.argument: str = argument
|
||||||
|
super().__init__(f'Thread "{argument}" not found.')
|
||||||
|
|
||||||
class BadColourArgument(BadArgument):
|
class BadColourArgument(BadArgument):
|
||||||
"""Exception raised when the colour is not valid.
|
"""Exception raised when the colour is not valid.
|
||||||
|
|
||||||
@@ -348,8 +381,8 @@ class BadColourArgument(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The colour supplied by the caller that was not valid
|
The colour supplied by the caller that was not valid
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Colour "{argument}" is invalid.')
|
super().__init__(f'Colour "{argument}" is invalid.')
|
||||||
|
|
||||||
BadColorArgument = BadColourArgument
|
BadColorArgument = BadColourArgument
|
||||||
@@ -366,8 +399,8 @@ class RoleNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The role supplied by the caller that was not found
|
The role supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Role "{argument}" not found.')
|
super().__init__(f'Role "{argument}" not found.')
|
||||||
|
|
||||||
class BadInviteArgument(BadArgument):
|
class BadInviteArgument(BadArgument):
|
||||||
@@ -377,8 +410,9 @@ class BadInviteArgument(BadArgument):
|
|||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, argument: str) -> None:
|
||||||
super().__init__('Invite is invalid or expired.')
|
self.argument: str = argument
|
||||||
|
super().__init__(f'Invite "{argument}" is invalid or expired.')
|
||||||
|
|
||||||
class EmojiNotFound(BadArgument):
|
class EmojiNotFound(BadArgument):
|
||||||
"""Exception raised when the bot can not find the emoji.
|
"""Exception raised when the bot can not find the emoji.
|
||||||
@@ -392,8 +426,8 @@ class EmojiNotFound(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The emoji supplied by the caller that was not found
|
The emoji supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Emoji "{argument}" not found.')
|
super().__init__(f'Emoji "{argument}" not found.')
|
||||||
|
|
||||||
class PartialEmojiConversionFailure(BadArgument):
|
class PartialEmojiConversionFailure(BadArgument):
|
||||||
@@ -409,10 +443,26 @@ class PartialEmojiConversionFailure(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The emoji supplied by the caller that did not match the regex
|
The emoji supplied by the caller that did not match the regex
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'Couldn\'t convert "{argument}" to PartialEmoji.')
|
super().__init__(f'Couldn\'t convert "{argument}" to PartialEmoji.')
|
||||||
|
|
||||||
|
class GuildStickerNotFound(BadArgument):
|
||||||
|
"""Exception raised when the bot can not find the sticker.
|
||||||
|
|
||||||
|
This inherits from :exc:`BadArgument`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
argument: :class:`str`
|
||||||
|
The sticker supplied by the caller that was not found
|
||||||
|
"""
|
||||||
|
def __init__(self, argument: str) -> None:
|
||||||
|
self.argument: str = argument
|
||||||
|
super().__init__(f'Sticker "{argument}" not found.')
|
||||||
|
|
||||||
class BadBoolArgument(BadArgument):
|
class BadBoolArgument(BadArgument):
|
||||||
"""Exception raised when a boolean argument was not convertable.
|
"""Exception raised when a boolean argument was not convertable.
|
||||||
|
|
||||||
@@ -425,8 +475,8 @@ class BadBoolArgument(BadArgument):
|
|||||||
argument: :class:`str`
|
argument: :class:`str`
|
||||||
The boolean argument supplied by the caller that is not in the predefined list
|
The boolean argument supplied by the caller that is not in the predefined list
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument: str) -> None:
|
||||||
self.argument = argument
|
self.argument: str = argument
|
||||||
super().__init__(f'{argument} is not a recognised boolean option')
|
super().__init__(f'{argument} is not a recognised boolean option')
|
||||||
|
|
||||||
class DisabledCommand(CommandError):
|
class DisabledCommand(CommandError):
|
||||||
@@ -447,8 +497,8 @@ class CommandInvokeError(CommandError):
|
|||||||
The original exception that was raised. You can also get this via
|
The original exception that was raised. You can also get this via
|
||||||
the ``__cause__`` attribute.
|
the ``__cause__`` attribute.
|
||||||
"""
|
"""
|
||||||
def __init__(self, e):
|
def __init__(self, e: Exception) -> None:
|
||||||
self.original = e
|
self.original: Exception = e
|
||||||
super().__init__(f'Command raised an exception: {e.__class__.__name__}: {e}')
|
super().__init__(f'Command raised an exception: {e.__class__.__name__}: {e}')
|
||||||
|
|
||||||
class CommandOnCooldown(CommandError):
|
class CommandOnCooldown(CommandError):
|
||||||
@@ -458,15 +508,18 @@ class CommandOnCooldown(CommandError):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
cooldown: ``Cooldown``
|
cooldown: :class:`.Cooldown`
|
||||||
A class with attributes ``rate``, ``per``, and ``type`` similar to
|
A class with attributes ``rate`` and ``per`` similar to the
|
||||||
the :func:`.cooldown` decorator.
|
:func:`.cooldown` decorator.
|
||||||
|
type: :class:`BucketType`
|
||||||
|
The type associated with the cooldown.
|
||||||
retry_after: :class:`float`
|
retry_after: :class:`float`
|
||||||
The amount of seconds to wait before you can retry again.
|
The amount of seconds to wait before you can retry again.
|
||||||
"""
|
"""
|
||||||
def __init__(self, cooldown, retry_after):
|
def __init__(self, cooldown: Cooldown, retry_after: float, type: BucketType) -> None:
|
||||||
self.cooldown = cooldown
|
self.cooldown: Cooldown = cooldown
|
||||||
self.retry_after = retry_after
|
self.retry_after: float = retry_after
|
||||||
|
self.type: BucketType = type
|
||||||
super().__init__(f'You are on cooldown. Try again in {retry_after:.2f}s')
|
super().__init__(f'You are on cooldown. Try again in {retry_after:.2f}s')
|
||||||
|
|
||||||
class MaxConcurrencyReached(CommandError):
|
class MaxConcurrencyReached(CommandError):
|
||||||
@@ -482,9 +535,9 @@ class MaxConcurrencyReached(CommandError):
|
|||||||
The bucket type passed to the :func:`.max_concurrency` decorator.
|
The bucket type passed to the :func:`.max_concurrency` decorator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, number, per):
|
def __init__(self, number: int, per: BucketType) -> None:
|
||||||
self.number = number
|
self.number: int = number
|
||||||
self.per = per
|
self.per: BucketType = per
|
||||||
name = per.name
|
name = per.name
|
||||||
suffix = 'per %s' % name if per.name != 'default' else 'globally'
|
suffix = 'per %s' % name if per.name != 'default' else 'globally'
|
||||||
plural = '%s times %s' if number > 1 else '%s time %s'
|
plural = '%s times %s' if number > 1 else '%s time %s'
|
||||||
@@ -504,8 +557,8 @@ class MissingRole(CheckFailure):
|
|||||||
The required role that is missing.
|
The required role that is missing.
|
||||||
This is the parameter passed to :func:`~.commands.has_role`.
|
This is the parameter passed to :func:`~.commands.has_role`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, missing_role):
|
def __init__(self, missing_role: Snowflake) -> None:
|
||||||
self.missing_role = missing_role
|
self.missing_role: Snowflake = missing_role
|
||||||
message = f'Role {missing_role!r} is required to run this command.'
|
message = f'Role {missing_role!r} is required to run this command.'
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
@@ -522,8 +575,8 @@ class BotMissingRole(CheckFailure):
|
|||||||
The required role that is missing.
|
The required role that is missing.
|
||||||
This is the parameter passed to :func:`~.commands.has_role`.
|
This is the parameter passed to :func:`~.commands.has_role`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, missing_role):
|
def __init__(self, missing_role: Snowflake) -> None:
|
||||||
self.missing_role = missing_role
|
self.missing_role: Snowflake = missing_role
|
||||||
message = f'Bot requires the role {missing_role!r} to run this command'
|
message = f'Bot requires the role {missing_role!r} to run this command'
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
@@ -541,8 +594,8 @@ class MissingAnyRole(CheckFailure):
|
|||||||
The roles that the invoker is missing.
|
The roles that the invoker is missing.
|
||||||
These are the parameters passed to :func:`~.commands.has_any_role`.
|
These are the parameters passed to :func:`~.commands.has_any_role`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, missing_roles):
|
def __init__(self, missing_roles: SnowflakeList) -> None:
|
||||||
self.missing_roles = missing_roles
|
self.missing_roles: SnowflakeList = missing_roles
|
||||||
|
|
||||||
missing = [f"'{role}'" for role in missing_roles]
|
missing = [f"'{role}'" for role in missing_roles]
|
||||||
|
|
||||||
@@ -570,8 +623,8 @@ class BotMissingAnyRole(CheckFailure):
|
|||||||
These are the parameters passed to :func:`~.commands.has_any_role`.
|
These are the parameters passed to :func:`~.commands.has_any_role`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, missing_roles):
|
def __init__(self, missing_roles: SnowflakeList) -> None:
|
||||||
self.missing_roles = missing_roles
|
self.missing_roles: SnowflakeList = missing_roles
|
||||||
|
|
||||||
missing = [f"'{role}'" for role in missing_roles]
|
missing = [f"'{role}'" for role in missing_roles]
|
||||||
|
|
||||||
@@ -592,11 +645,11 @@ class NSFWChannelRequired(CheckFailure):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
channel: :class:`discord.abc.GuildChannel`
|
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):
|
def __init__(self, channel: Union[GuildChannel, Thread]) -> None:
|
||||||
self.channel = channel
|
self.channel: Union[GuildChannel, Thread] = channel
|
||||||
super().__init__(f"Channel '{channel}' needs to be NSFW for this command to work.")
|
super().__init__(f"Channel '{channel}' needs to be NSFW for this command to work.")
|
||||||
|
|
||||||
class MissingPermissions(CheckFailure):
|
class MissingPermissions(CheckFailure):
|
||||||
@@ -607,13 +660,13 @@ class MissingPermissions(CheckFailure):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
missing_perms: :class:`list`
|
missing_permissions: List[:class:`str`]
|
||||||
The required permissions that are missing.
|
The required permissions that are missing.
|
||||||
"""
|
"""
|
||||||
def __init__(self, missing_perms, *args):
|
def __init__(self, missing_permissions: List[str], *args: Any) -> None:
|
||||||
self.missing_perms = missing_perms
|
self.missing_permissions: List[str] = missing_permissions
|
||||||
|
|
||||||
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_perms]
|
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
|
||||||
|
|
||||||
if len(missing) > 2:
|
if len(missing) > 2:
|
||||||
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
||||||
@@ -630,13 +683,13 @@ class BotMissingPermissions(CheckFailure):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
missing_perms: :class:`list`
|
missing_permissions: List[:class:`str`]
|
||||||
The required permissions that are missing.
|
The required permissions that are missing.
|
||||||
"""
|
"""
|
||||||
def __init__(self, missing_perms, *args):
|
def __init__(self, missing_permissions: List[str], *args: Any) -> None:
|
||||||
self.missing_perms = missing_perms
|
self.missing_permissions: List[str] = missing_permissions
|
||||||
|
|
||||||
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_perms]
|
missing = [perm.replace('_', ' ').replace('guild', 'server').title() for perm in missing_permissions]
|
||||||
|
|
||||||
if len(missing) > 2:
|
if len(missing) > 2:
|
||||||
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
||||||
@@ -660,10 +713,10 @@ class BadUnionArgument(UserInputError):
|
|||||||
errors: List[:class:`CommandError`]
|
errors: List[:class:`CommandError`]
|
||||||
A list of errors that were caught from failing the conversion.
|
A list of errors that were caught from failing the conversion.
|
||||||
"""
|
"""
|
||||||
def __init__(self, param, converters, errors):
|
def __init__(self, param: Parameter, converters: Tuple[Type, ...], errors: List[CommandError]) -> None:
|
||||||
self.param = param
|
self.param: Parameter = param
|
||||||
self.converters = converters
|
self.converters: Tuple[Type, ...] = converters
|
||||||
self.errors = errors
|
self.errors: List[CommandError] = errors
|
||||||
|
|
||||||
def _get_name(x):
|
def _get_name(x):
|
||||||
try:
|
try:
|
||||||
@@ -698,10 +751,10 @@ class BadLiteralArgument(UserInputError):
|
|||||||
errors: List[:class:`CommandError`]
|
errors: List[:class:`CommandError`]
|
||||||
A list of errors that were caught from failing the conversion.
|
A list of errors that were caught from failing the conversion.
|
||||||
"""
|
"""
|
||||||
def __init__(self, param, literals, errors):
|
def __init__(self, param: Parameter, literals: Tuple[Any, ...], errors: List[CommandError]) -> None:
|
||||||
self.param = param
|
self.param: Parameter = param
|
||||||
self.literals = literals
|
self.literals: Tuple[Any, ...] = literals
|
||||||
self.errors = errors
|
self.errors: List[CommandError] = errors
|
||||||
|
|
||||||
to_string = [repr(l) for l in literals]
|
to_string = [repr(l) for l in literals]
|
||||||
if len(to_string) > 2:
|
if len(to_string) > 2:
|
||||||
@@ -731,8 +784,8 @@ class UnexpectedQuoteError(ArgumentParsingError):
|
|||||||
quote: :class:`str`
|
quote: :class:`str`
|
||||||
The quote mark that was found inside the non-quoted string.
|
The quote mark that was found inside the non-quoted string.
|
||||||
"""
|
"""
|
||||||
def __init__(self, quote):
|
def __init__(self, quote: str) -> None:
|
||||||
self.quote = quote
|
self.quote: str = quote
|
||||||
super().__init__(f'Unexpected quote mark, {quote!r}, in non-quoted string')
|
super().__init__(f'Unexpected quote mark, {quote!r}, in non-quoted string')
|
||||||
|
|
||||||
class InvalidEndOfQuotedStringError(ArgumentParsingError):
|
class InvalidEndOfQuotedStringError(ArgumentParsingError):
|
||||||
@@ -746,8 +799,8 @@ class InvalidEndOfQuotedStringError(ArgumentParsingError):
|
|||||||
char: :class:`str`
|
char: :class:`str`
|
||||||
The character found instead of the expected string.
|
The character found instead of the expected string.
|
||||||
"""
|
"""
|
||||||
def __init__(self, char):
|
def __init__(self, char: str) -> None:
|
||||||
self.char = char
|
self.char: str = char
|
||||||
super().__init__(f'Expected space after closing quotation but received {char!r}')
|
super().__init__(f'Expected space after closing quotation but received {char!r}')
|
||||||
|
|
||||||
class ExpectedClosingQuoteError(ArgumentParsingError):
|
class ExpectedClosingQuoteError(ArgumentParsingError):
|
||||||
@@ -761,8 +814,8 @@ class ExpectedClosingQuoteError(ArgumentParsingError):
|
|||||||
The quote character expected.
|
The quote character expected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, close_quote):
|
def __init__(self, close_quote: str) -> None:
|
||||||
self.close_quote = close_quote
|
self.close_quote: str = close_quote
|
||||||
super().__init__(f'Expected closing {close_quote}.')
|
super().__init__(f'Expected closing {close_quote}.')
|
||||||
|
|
||||||
class ExtensionError(DiscordException):
|
class ExtensionError(DiscordException):
|
||||||
@@ -775,8 +828,8 @@ class ExtensionError(DiscordException):
|
|||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The extension that had an error.
|
The extension that had an error.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message=None, *args, name):
|
def __init__(self, message: Optional[str] = None, *args: Any, name: str) -> None:
|
||||||
self.name = name
|
self.name: str = name
|
||||||
message = message or f'Extension {name!r} had an error.'
|
message = message or f'Extension {name!r} had an error.'
|
||||||
# clean-up @everyone and @here mentions
|
# clean-up @everyone and @here mentions
|
||||||
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
|
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
|
||||||
@@ -787,7 +840,7 @@ class ExtensionAlreadyLoaded(ExtensionError):
|
|||||||
|
|
||||||
This inherits from :exc:`ExtensionError`
|
This inherits from :exc:`ExtensionError`
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name: str) -> None:
|
||||||
super().__init__(f'Extension {name!r} is already loaded.', name=name)
|
super().__init__(f'Extension {name!r} is already loaded.', name=name)
|
||||||
|
|
||||||
class ExtensionNotLoaded(ExtensionError):
|
class ExtensionNotLoaded(ExtensionError):
|
||||||
@@ -795,7 +848,7 @@ class ExtensionNotLoaded(ExtensionError):
|
|||||||
|
|
||||||
This inherits from :exc:`ExtensionError`
|
This inherits from :exc:`ExtensionError`
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name: str) -> None:
|
||||||
super().__init__(f'Extension {name!r} has not been loaded.', name=name)
|
super().__init__(f'Extension {name!r} has not been loaded.', name=name)
|
||||||
|
|
||||||
class NoEntryPointError(ExtensionError):
|
class NoEntryPointError(ExtensionError):
|
||||||
@@ -803,7 +856,7 @@ class NoEntryPointError(ExtensionError):
|
|||||||
|
|
||||||
This inherits from :exc:`ExtensionError`
|
This inherits from :exc:`ExtensionError`
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name: str) -> None:
|
||||||
super().__init__(f"Extension {name!r} has no 'setup' function.", name=name)
|
super().__init__(f"Extension {name!r} has no 'setup' function.", name=name)
|
||||||
|
|
||||||
class ExtensionFailed(ExtensionError):
|
class ExtensionFailed(ExtensionError):
|
||||||
@@ -819,8 +872,8 @@ class ExtensionFailed(ExtensionError):
|
|||||||
The original exception that was raised. You can also get this via
|
The original exception that was raised. You can also get this via
|
||||||
the ``__cause__`` attribute.
|
the ``__cause__`` attribute.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, original):
|
def __init__(self, name: str, original: Exception) -> None:
|
||||||
self.original = original
|
self.original: Exception = original
|
||||||
msg = f'Extension {name!r} raised an error: {original.__class__.__name__}: {original}'
|
msg = f'Extension {name!r} raised an error: {original.__class__.__name__}: {original}'
|
||||||
super().__init__(msg, name=name)
|
super().__init__(msg, name=name)
|
||||||
|
|
||||||
@@ -837,7 +890,7 @@ class ExtensionNotFound(ExtensionError):
|
|||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The extension that had the error.
|
The extension that had the error.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name: str) -> None:
|
||||||
msg = f'Extension {name!r} could not be loaded.'
|
msg = f'Extension {name!r} could not be loaded.'
|
||||||
super().__init__(msg, name=name)
|
super().__init__(msg, name=name)
|
||||||
|
|
||||||
@@ -856,9 +909,9 @@ class CommandRegistrationError(ClientException):
|
|||||||
alias_conflict: :class:`bool`
|
alias_conflict: :class:`bool`
|
||||||
Whether the name that conflicts is an alias of the command we try to add.
|
Whether the name that conflicts is an alias of the command we try to add.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, *, alias_conflict=False):
|
def __init__(self, name: str, *, alias_conflict: bool = False) -> None:
|
||||||
self.name = name
|
self.name: str = name
|
||||||
self.alias_conflict = alias_conflict
|
self.alias_conflict: bool = alias_conflict
|
||||||
type_ = 'alias' if alias_conflict else 'command'
|
type_ = 'alias' if alias_conflict else 'command'
|
||||||
super().__init__(f'The {type_} {name} is already an existing command or alias.')
|
super().__init__(f'The {type_} {name} is already an existing command or alias.')
|
||||||
|
|
||||||
@@ -885,17 +938,25 @@ class TooManyFlags(FlagError):
|
|||||||
values: List[:class:`str`]
|
values: List[:class:`str`]
|
||||||
The values that were passed.
|
The values that were passed.
|
||||||
"""
|
"""
|
||||||
def __init__(self, flag, values):
|
def __init__(self, flag: Flag, values: List[str]) -> None:
|
||||||
self.flag = flag
|
self.flag: Flag = flag
|
||||||
self.values = values
|
self.values: List[str] = values
|
||||||
super().__init__(f'Too many flag values, expected {flag.max_args} but received {len(values)}.')
|
super().__init__(f'Too many flag values, expected {flag.max_args} but received {len(values)}.')
|
||||||
|
|
||||||
class BadFlagArgument(FlagError):
|
class BadFlagArgument(FlagError):
|
||||||
"""An exception raised when a flag failed to convert a value.
|
"""An exception raised when a flag failed to convert a value.
|
||||||
|
|
||||||
|
This inherits from :exc:`FlagError`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
flag: :class:`~discord.ext.commands.Flag`
|
||||||
|
The flag that failed to convert.
|
||||||
"""
|
"""
|
||||||
def __init__(self, flag):
|
def __init__(self, flag: Flag) -> None:
|
||||||
self.flag = flag
|
self.flag: Flag = flag
|
||||||
try:
|
try:
|
||||||
name = flag.annotation.__name__
|
name = flag.annotation.__name__
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -915,8 +976,8 @@ class MissingRequiredFlag(FlagError):
|
|||||||
flag: :class:`~discord.ext.commands.Flag`
|
flag: :class:`~discord.ext.commands.Flag`
|
||||||
The required flag that was not found.
|
The required flag that was not found.
|
||||||
"""
|
"""
|
||||||
def __init__(self, flag):
|
def __init__(self, flag: Flag) -> None:
|
||||||
self.flag = flag
|
self.flag: Flag = flag
|
||||||
super().__init__(f'Flag {flag.name!r} is required and missing')
|
super().__init__(f'Flag {flag.name!r} is required and missing')
|
||||||
|
|
||||||
class MissingFlagArgument(FlagError):
|
class MissingFlagArgument(FlagError):
|
||||||
@@ -931,6 +992,6 @@ class MissingFlagArgument(FlagError):
|
|||||||
flag: :class:`~discord.ext.commands.Flag`
|
flag: :class:`~discord.ext.commands.Flag`
|
||||||
The flag that did not get a value.
|
The flag that did not get a value.
|
||||||
"""
|
"""
|
||||||
def __init__(self, flag):
|
def __init__(self, flag: Flag) -> None:
|
||||||
self.flag = flag
|
self.flag: Flag = flag
|
||||||
super().__init__(f'Flag {flag.name!r} does not have an argument')
|
super().__init__(f'Flag {flag.name!r} does not have an argument')
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -30,21 +30,17 @@ from typing import (
|
|||||||
Any,
|
Any,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
Callable,
|
Callable,
|
||||||
Coroutine,
|
|
||||||
Generic,
|
Generic,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
TYPE_CHECKING,
|
|
||||||
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
|
||||||
|
|
||||||
@@ -52,26 +48,15 @@ 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
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from typing_extensions import Concatenate, ParamSpec
|
|
||||||
|
|
||||||
P = ParamSpec("P")
|
|
||||||
else:
|
|
||||||
P = TypeVar("P") # hacky runtime fix
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'loop',
|
'loop',
|
||||||
)
|
)
|
||||||
|
|
||||||
C = TypeVar('C')
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
_coro = Coroutine[Any, Any, T]
|
|
||||||
_func = Callable[..., Awaitable[Any]]
|
_func = Callable[..., Awaitable[Any]]
|
||||||
|
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:
|
||||||
@@ -88,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:
|
||||||
@@ -99,29 +84,31 @@ class SleepHandle:
|
|||||||
self.future.cancel()
|
self.future.cancel()
|
||||||
|
|
||||||
|
|
||||||
class Loop(Generic[C, P, T]):
|
class Loop(Generic[LF]):
|
||||||
"""A background task helper that abstracts the loop and reconnection logic for you.
|
"""A background task helper that abstracts the loop and reconnection logic for you.
|
||||||
|
|
||||||
The main interface to create this is through :func:`loop`.
|
The main interface to create this is through :func:`loop`.
|
||||||
"""
|
"""
|
||||||
def __init__(self,
|
|
||||||
coro: Callable[P, _coro[T]],
|
def __init__(
|
||||||
|
self,
|
||||||
|
coro: LF,
|
||||||
seconds: float,
|
seconds: float,
|
||||||
hours: float,
|
hours: float,
|
||||||
minutes: float,
|
minutes: float,
|
||||||
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: Callable[P, _coro[T]] = 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: Optional[C] = None
|
self._injected = None
|
||||||
self._valid_exception = (
|
self._valid_exception = (
|
||||||
OSError,
|
OSError,
|
||||||
discord.GatewayNotFound,
|
discord.GatewayNotFound,
|
||||||
@@ -141,7 +128,7 @@ class Loop(Generic[C, P, T]):
|
|||||||
|
|
||||||
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):
|
||||||
@@ -157,9 +144,8 @@ class Loop(Generic[C, P, T]):
|
|||||||
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:
|
||||||
@@ -217,11 +203,11 @@ class Loop(Generic[C, P, T]):
|
|||||||
self._stop_next_iteration = False
|
self._stop_next_iteration = False
|
||||||
self._has_failed = False
|
self._has_failed = False
|
||||||
|
|
||||||
def __get__(self, obj: C, objtype: Type[C]) -> Loop[C, P, T]:
|
def __get__(self, obj: T, objtype: Type[T]) -> Loop[LF]:
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
copy = Loop[C, P, T](
|
copy: Loop[LF] = Loop(
|
||||||
self.coro,
|
self.coro,
|
||||||
seconds=self._seconds,
|
seconds=self._seconds,
|
||||||
hours=self._hours,
|
hours=self._hours,
|
||||||
@@ -289,13 +275,13 @@ class Loop(Generic[C, P, T]):
|
|||||||
|
|
||||||
.. 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
|
||||||
return self._next_iteration
|
return self._next_iteration
|
||||||
|
|
||||||
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
async def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
Calls the internal callback that the task holds.
|
Calls the internal callback that the task holds.
|
||||||
@@ -315,7 +301,7 @@ class Loop(Generic[C, P, T]):
|
|||||||
|
|
||||||
return await self.coro(*args, **kwargs)
|
return await self.coro(*args, **kwargs)
|
||||||
|
|
||||||
def start(self, *args: P.args, **kwargs: P.kwargs) -> 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
|
||||||
@@ -336,13 +322,13 @@ class Loop(Generic[C, P, T]):
|
|||||||
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))
|
||||||
@@ -366,7 +352,7 @@ class Loop(Generic[C, P, T]):
|
|||||||
|
|
||||||
.. 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:
|
||||||
@@ -377,7 +363,7 @@ class Loop(Generic[C, P, T]):
|
|||||||
if self._can_be_cancelled():
|
if self._can_be_cancelled():
|
||||||
self._task.cancel()
|
self._task.cancel()
|
||||||
|
|
||||||
def restart(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
def restart(self, *args: Any, **kwargs: Any) -> None:
|
||||||
r"""A convenience method to restart the internal task.
|
r"""A convenience method to restart the internal task.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -388,12 +374,12 @@ class Loop(Generic[C, P, T]):
|
|||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
\*args
|
\*args
|
||||||
The arguments to to use.
|
The arguments to use.
|
||||||
\*\*kwargs
|
\*\*kwargs
|
||||||
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)
|
||||||
|
|
||||||
@@ -456,9 +442,9 @@ class Loop(Generic[C, P, T]):
|
|||||||
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."""
|
||||||
@@ -476,7 +462,7 @@ class Loop(Generic[C, P, T]):
|
|||||||
|
|
||||||
.. 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]
|
||||||
@@ -570,7 +556,9 @@ class Loop(Generic[C, P, T]):
|
|||||||
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]
|
||||||
|
|
||||||
@@ -578,7 +566,7 @@ class Loop(Generic[C, P, T]):
|
|||||||
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)
|
||||||
@@ -586,12 +574,14 @@ class Loop(Generic[C, P, T]):
|
|||||||
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
|
||||||
@@ -607,17 +597,21 @@ class Loop(Generic[C, P, T]):
|
|||||||
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
|
||||||
@@ -701,8 +695,8 @@ 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[[Union[Callable[Concatenate[Type[C], P], _coro[T]], Callable[P, _coro[T]]]], Loop[C, P, T]]:
|
) -> 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`.
|
||||||
|
|
||||||
@@ -734,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`.
|
||||||
|
|
||||||
@@ -746,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: Union[Callable[Concatenate[Type[C], P], _coro[T]], Callable[P, _coro[T]]]) -> Loop[C, P, T]:
|
|
||||||
kwargs = {
|
def decorator(func: LF) -> Loop[LF]:
|
||||||
'seconds': seconds,
|
return Loop[LF](
|
||||||
'minutes': minutes,
|
func,
|
||||||
'hours': hours,
|
seconds=seconds,
|
||||||
'count': count,
|
minutes=minutes,
|
||||||
'time': time,
|
hours=hours,
|
||||||
'reconnect': reconnect,
|
count=count,
|
||||||
'loop': loop,
|
time=time,
|
||||||
}
|
reconnect=reconnect,
|
||||||
return Loop[C, P, T](func, **kwargs)
|
loop=loop,
|
||||||
|
)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ FV = TypeVar('FV', bound='flag_value')
|
|||||||
BF = TypeVar('BF', bound='BaseFlags')
|
BF = TypeVar('BF', bound='BaseFlags')
|
||||||
|
|
||||||
|
|
||||||
class flag_value(Generic[BF]):
|
class flag_value:
|
||||||
def __init__(self, func: Callable[[Any], int]):
|
def __init__(self, func: Callable[[Any], int]):
|
||||||
self.flag = func(None)
|
self.flag = func(None)
|
||||||
self.__doc__ = func.__doc__
|
self.__doc__ = func.__doc__
|
||||||
@@ -205,7 +205,7 @@ class SystemChannelFlags(BaseFlags):
|
|||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def premium_subscriptions(self):
|
def premium_subscriptions(self):
|
||||||
""":class:`bool`: Returns ``True`` if the system channel is used for Nitro boosting notifications."""
|
""":class:`bool`: Returns ``True`` if the system channel is used for "Nitro boosting" notifications."""
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
@@ -287,6 +287,15 @@ class MessageFlags(BaseFlags):
|
|||||||
"""
|
"""
|
||||||
return 32
|
return 32
|
||||||
|
|
||||||
|
@flag_value
|
||||||
|
def ephemeral(self):
|
||||||
|
""":class:`bool`: Returns ``True`` if the source message is ephemeral.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
return 64
|
||||||
|
|
||||||
|
|
||||||
@fill_with_flags()
|
@fill_with_flags()
|
||||||
class PublicUserFlags(BaseFlags):
|
class PublicUserFlags(BaseFlags):
|
||||||
r"""Wraps up the Discord User Public flags.
|
r"""Wraps up the Discord User Public flags.
|
||||||
@@ -471,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.
|
||||||
@@ -515,7 +514,7 @@ class Intents(BaseFlags):
|
|||||||
|
|
||||||
- :func:`on_member_join`
|
- :func:`on_member_join`
|
||||||
- :func:`on_member_remove`
|
- :func:`on_member_remove`
|
||||||
- :func:`on_member_update` (nickname, roles)
|
- :func:`on_member_update`
|
||||||
- :func:`on_user_update`
|
- :func:`on_user_update`
|
||||||
|
|
||||||
This also corresponds to the following attributes and classes in terms of cache:
|
This also corresponds to the following attributes and classes in terms of cache:
|
||||||
@@ -557,18 +556,34 @@ class Intents(BaseFlags):
|
|||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def emojis(self):
|
def emojis(self):
|
||||||
""":class:`bool`: Whether guild emoji related events are enabled.
|
""":class:`bool`: Alias of :attr:`.emojis_and_stickers`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Changed to an alias.
|
||||||
|
"""
|
||||||
|
return 1 << 3
|
||||||
|
|
||||||
|
@alias_flag_value
|
||||||
|
def emojis_and_stickers(self):
|
||||||
|
""":class:`bool`: Whether guild emoji and sticker related events are enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
This corresponds to the following events:
|
This corresponds to the following events:
|
||||||
|
|
||||||
- :func:`on_guild_emojis_update`
|
- :func:`on_guild_emojis_update`
|
||||||
|
- :func:`on_guild_stickers_update`
|
||||||
|
|
||||||
This also corresponds to the following attributes and classes in terms of cache:
|
This also corresponds to the following attributes and classes in terms of cache:
|
||||||
|
|
||||||
- :class:`Emoji`
|
- :class:`Emoji`
|
||||||
|
- :class:`GuildSticker`
|
||||||
- :meth:`Client.get_emoji`
|
- :meth:`Client.get_emoji`
|
||||||
|
- :meth:`Client.get_sticker`
|
||||||
- :meth:`Client.emojis`
|
- :meth:`Client.emojis`
|
||||||
|
- :meth:`Client.stickers`
|
||||||
- :attr:`Guild.emojis`
|
- :attr:`Guild.emojis`
|
||||||
|
- :attr:`Guild.stickers`
|
||||||
"""
|
"""
|
||||||
return 1 << 3
|
return 1 << 3
|
||||||
|
|
||||||
@@ -625,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
|
||||||
|
|
||||||
@@ -634,7 +653,7 @@ class Intents(BaseFlags):
|
|||||||
|
|
||||||
This corresponds to the following events:
|
This corresponds to the following events:
|
||||||
|
|
||||||
- :func:`on_member_update` (activities, status)
|
- :func:`on_presence_update`
|
||||||
|
|
||||||
This also corresponds to the following attributes and classes in terms of cache:
|
This also corresponds to the following attributes and classes in terms of cache:
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
import asyncio
|
import asyncio
|
||||||
from collections import namedtuple, deque
|
from collections import namedtuple, deque
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
@@ -41,7 +40,7 @@ from .activity import BaseActivity
|
|||||||
from .enums import SpeakingState
|
from .enums import SpeakingState
|
||||||
from .errors import ConnectionClosed, InvalidArgument
|
from .errors import ConnectionClosed, InvalidArgument
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'DiscordWebSocket',
|
'DiscordWebSocket',
|
||||||
@@ -102,7 +101,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)
|
||||||
|
|
||||||
|
|
||||||
@@ -130,20 +129,20 @@ class KeepAliveHandler(threading.Thread):
|
|||||||
def run(self):
|
def run(self):
|
||||||
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:
|
||||||
@@ -162,7 +161,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()
|
||||||
@@ -186,7 +185,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, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -294,6 +293,12 @@ class DiscordWebSocket:
|
|||||||
def is_ratelimited(self):
|
def is_ratelimited(self):
|
||||||
return self._rate_limiter.is_ratelimited()
|
return self._rate_limiter.is_ratelimited()
|
||||||
|
|
||||||
|
def debug_log_receive(self, data, /):
|
||||||
|
self._dispatch('socket_raw_receive', data)
|
||||||
|
|
||||||
|
def log_receive(self, _, /):
|
||||||
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_client(cls, client, *, initial=False, gateway=None, shard_id=None, session=None, sequence=None, resume=False):
|
async def from_client(cls, client, *, initial=False, gateway=None, shard_id=None, session=None, sequence=None, resume=False):
|
||||||
"""Creates a main websocket for Discord from a :class:`Client`.
|
"""Creates a main websocket for Discord from a :class:`Client`.
|
||||||
@@ -319,9 +324,13 @@ class DiscordWebSocket:
|
|||||||
ws.sequence = sequence
|
ws.sequence = sequence
|
||||||
ws._max_heartbeat_timeout = client._connection.heartbeat_timeout
|
ws._max_heartbeat_timeout = client._connection.heartbeat_timeout
|
||||||
|
|
||||||
|
if client._enable_debug_events:
|
||||||
|
ws.send = ws.debug_send
|
||||||
|
ws.log_receive = ws.debug_log_receive
|
||||||
|
|
||||||
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()
|
||||||
@@ -394,7 +403,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):
|
async def resume(self):
|
||||||
"""Sends the RESUME packet."""
|
"""Sends the RESUME packet."""
|
||||||
@@ -408,11 +417,9 @@ 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):
|
|
||||||
self._dispatch('socket_raw_receive', msg)
|
|
||||||
|
|
||||||
|
async def received_message(self, msg, /):
|
||||||
if type(msg) is bytes:
|
if type(msg) is bytes:
|
||||||
self._buffer.extend(msg)
|
self._buffer.extend(msg)
|
||||||
|
|
||||||
@@ -421,10 +428,14 @@ 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 = json.loads(msg)
|
|
||||||
|
|
||||||
log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg)
|
self.log_receive(msg)
|
||||||
self._dispatch('socket_response', msg)
|
msg = utils._from_json(msg)
|
||||||
|
|
||||||
|
_log.debug('For Shard ID %s: WebSocket Event: %s', self.shard_id, msg)
|
||||||
|
event = msg.get('t')
|
||||||
|
if event:
|
||||||
|
self._dispatch('socket_event_type', event)
|
||||||
|
|
||||||
op = msg.get('op')
|
op = msg.get('op')
|
||||||
data = msg.get('d')
|
data = msg.get('d')
|
||||||
@@ -440,7 +451,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)
|
||||||
|
|
||||||
@@ -470,35 +481,33 @@ 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
|
||||||
|
|
||||||
event = msg.get('t')
|
|
||||||
|
|
||||||
if event == 'READY':
|
if event == 'READY':
|
||||||
self._trace = trace = data.get('_trace', [])
|
self._trace = trace = data.get('_trace', [])
|
||||||
self.sequence = msg['s']
|
self.sequence = msg['s']
|
||||||
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)
|
||||||
|
|
||||||
@@ -552,10 +561,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
|
||||||
@@ -564,25 +573,29 @@ 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 send(self, data):
|
async def debug_send(self, data, /):
|
||||||
await self._rate_limiter.block()
|
await self._rate_limiter.block()
|
||||||
self._dispatch('socket_raw_send', data)
|
self._dispatch('socket_raw_send', data)
|
||||||
await self.socket.send_str(data)
|
await self.socket.send_str(data)
|
||||||
|
|
||||||
|
async def send(self, data, /):
|
||||||
|
await self._rate_limiter.block()
|
||||||
|
await self.socket.send_str(data)
|
||||||
|
|
||||||
async def send_as_json(self, data):
|
async def send_as_json(self, data):
|
||||||
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
|
||||||
@@ -590,12 +603,12 @@ class DiscordWebSocket:
|
|||||||
async def send_heartbeat(self, data):
|
async def send_heartbeat(self, data):
|
||||||
# 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
|
||||||
|
|
||||||
async def change_presence(self, *, activity=None, status=None, afk=False, since=0.0):
|
async def change_presence(self, *, activity=None, status=None, since=0.0):
|
||||||
if activity is not None:
|
if activity is not None:
|
||||||
if not isinstance(activity, BaseActivity):
|
if not isinstance(activity, BaseActivity):
|
||||||
raise InvalidArgument('activity must derive from BaseActivity.')
|
raise InvalidArgument('activity must derive from BaseActivity.')
|
||||||
@@ -610,14 +623,14 @@ class DiscordWebSocket:
|
|||||||
'op': self.PRESENCE,
|
'op': self.PRESENCE,
|
||||||
'd': {
|
'd': {
|
||||||
'activities': activity,
|
'activities': activity,
|
||||||
'afk': afk,
|
'afk': False,
|
||||||
'since': since,
|
'since': since,
|
||||||
'status': status
|
'status': status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, query=None, *, limit, user_ids=None, presences=False, nonce=None):
|
async def request_chunks(self, guild_id, query=None, *, limit, user_ids=None, presences=False, nonce=None):
|
||||||
@@ -653,7 +666,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=4000):
|
async def close(self, code=4000):
|
||||||
@@ -721,8 +734,8 @@ class DiscordVoiceWebSocket:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def send_as_json(self, data):
|
async def send_as_json(self, data):
|
||||||
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
|
||||||
|
|
||||||
@@ -807,7 +820,7 @@ class DiscordVoiceWebSocket:
|
|||||||
await self.send_as_json(payload)
|
await self.send_as_json(payload)
|
||||||
|
|
||||||
async def received_message(self, msg):
|
async def received_message(self, msg):
|
||||||
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')
|
||||||
|
|
||||||
@@ -816,7 +829,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)
|
||||||
@@ -839,7 +852,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
|
||||||
@@ -847,15 +860,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):
|
def latency(self):
|
||||||
@@ -873,7 +886,7 @@ 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):
|
async def load_secret_key(self, data):
|
||||||
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)
|
||||||
@@ -882,12 +895,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(json.loads(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=1000):
|
async def close(self, code=1000):
|
||||||
|
|||||||
346
discord/guild.py
346
discord/guild.py
@@ -25,6 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import unicodedata
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
@@ -51,6 +52,7 @@ from .colour import Colour
|
|||||||
from .errors import InvalidArgument, ClientException
|
from .errors import InvalidArgument, ClientException
|
||||||
from .channel import *
|
from .channel import *
|
||||||
from .channel import _guild_channel_factory
|
from .channel import _guild_channel_factory
|
||||||
|
from .channel import _threaded_guild_channel_factory
|
||||||
from .enums import (
|
from .enums import (
|
||||||
AuditLogAction,
|
AuditLogAction,
|
||||||
VideoQualityMode,
|
VideoQualityMode,
|
||||||
@@ -71,7 +73,10 @@ from .asset import Asset
|
|||||||
from .flags import SystemChannelFlags
|
from .flags import SystemChannelFlags
|
||||||
from .integrations import Integration, _integration_factory
|
from .integrations import Integration, _integration_factory
|
||||||
from .stage_instance import StageInstance
|
from .stage_instance import StageInstance
|
||||||
from .threads import Thread
|
from .threads import Thread, ThreadMember
|
||||||
|
from .sticker import GuildSticker
|
||||||
|
from .file import File
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Guild',
|
'Guild',
|
||||||
@@ -81,7 +86,7 @@ MISSING = utils.MISSING
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .abc import Snowflake, SnowflakeTime
|
from .abc import Snowflake, SnowflakeTime
|
||||||
from .types.guild import Ban as BanPayload, Guild as GuildPayload, MFALevel
|
from .types.guild import Ban as BanPayload, Guild as GuildPayload, MFALevel, GuildFeature
|
||||||
from .types.threads import (
|
from .types.threads import (
|
||||||
Thread as ThreadPayload,
|
Thread as ThreadPayload,
|
||||||
)
|
)
|
||||||
@@ -107,6 +112,7 @@ class BanEntry(NamedTuple):
|
|||||||
|
|
||||||
class _GuildLimit(NamedTuple):
|
class _GuildLimit(NamedTuple):
|
||||||
emoji: int
|
emoji: int
|
||||||
|
stickers: int
|
||||||
bitrate: float
|
bitrate: float
|
||||||
filesize: int
|
filesize: int
|
||||||
|
|
||||||
@@ -134,12 +140,20 @@ 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`
|
||||||
The guild name.
|
The guild name.
|
||||||
emojis: Tuple[:class:`Emoji`, ...]
|
emojis: Tuple[:class:`Emoji`, ...]
|
||||||
All emojis that the guild owns.
|
All emojis that the guild owns.
|
||||||
|
stickers: Tuple[:class:`GuildSticker`, ...]
|
||||||
|
All stickers that the guild owns.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
region: :class:`VoiceRegion`
|
region: :class:`VoiceRegion`
|
||||||
The region the guild belongs on. There is a chance that the region
|
The region the guild belongs on. There is a chance that the region
|
||||||
will be a :class:`str` if the value is not recognised by the enumerator.
|
will be a :class:`str` if the value is not recognised by the enumerator.
|
||||||
@@ -182,26 +196,33 @@ class Guild(Hashable):
|
|||||||
default_notifications: :class:`NotificationLevel`
|
default_notifications: :class:`NotificationLevel`
|
||||||
The guild's notification settings.
|
The guild's notification settings.
|
||||||
features: List[:class:`str`]
|
features: List[:class:`str`]
|
||||||
A list of features that the guild has. They are currently as follows:
|
A list of features that the guild has. The features that a guild can have are
|
||||||
|
subject to arbitrary change by Discord.
|
||||||
|
|
||||||
- ``VIP_REGIONS``: Guild has VIP voice regions
|
They are currently as follows:
|
||||||
- ``VANITY_URL``: Guild can have a vanity invite URL (e.g. discord.gg/discord-api)
|
|
||||||
- ``INVITE_SPLASH``: Guild's invite page can have a special splash.
|
- ``ANIMATED_ICON``: Guild can upload an animated icon.
|
||||||
- ``VERIFIED``: Guild is a verified server.
|
- ``BANNER``: Guild can upload and use a banner. (i.e. :attr:`.banner`)
|
||||||
- ``PARTNERED``: Guild is a partnered server.
|
- ``COMMERCE``: Guild can sell things using store channels.
|
||||||
- ``MORE_EMOJI``: Guild is allowed to have more than 50 custom emoji.
|
- ``COMMUNITY``: Guild is a community server.
|
||||||
- ``DISCOVERABLE``: Guild shows up in Server Discovery.
|
- ``DISCOVERABLE``: Guild shows up in Server Discovery.
|
||||||
- ``FEATURABLE``: Guild is able to be featured in Server Discovery.
|
- ``FEATURABLE``: Guild is able to be featured in Server Discovery.
|
||||||
- ``COMMUNITY``: Guild is a community server.
|
- ``INVITE_SPLASH``: Guild's invite page can have a special splash.
|
||||||
- ``COMMERCE``: Guild can sell things using store channels.
|
|
||||||
- ``PUBLIC``: Guild is a public guild.
|
|
||||||
- ``NEWS``: Guild can create news channels.
|
|
||||||
- ``BANNER``: Guild can upload and use a banner. (i.e. :attr:`.banner`)
|
|
||||||
- ``ANIMATED_ICON``: Guild can upload an animated icon.
|
|
||||||
- ``PUBLIC_DISABLED``: Guild cannot be public.
|
|
||||||
- ``WELCOME_SCREEN_ENABLED``: Guild has enabled the welcome screen
|
|
||||||
- ``MEMBER_VERIFICATION_GATE_ENABLED``: Guild has Membership Screening enabled.
|
- ``MEMBER_VERIFICATION_GATE_ENABLED``: Guild has Membership Screening enabled.
|
||||||
|
- ``MONETIZATION_ENABLED``: Guild has enabled monetization.
|
||||||
|
- ``MORE_EMOJI``: Guild has increased custom emoji slots.
|
||||||
|
- ``MORE_STICKERS``: Guild has increased custom sticker slots.
|
||||||
|
- ``NEWS``: Guild can create news channels.
|
||||||
|
- ``PARTNERED``: Guild is a partnered server.
|
||||||
- ``PREVIEW_ENABLED``: Guild can be viewed before being accepted via Membership Screening.
|
- ``PREVIEW_ENABLED``: Guild can be viewed before being accepted via Membership Screening.
|
||||||
|
- ``PRIVATE_THREADS``: Guild has access to create private threads.
|
||||||
|
- ``SEVEN_DAY_THREAD_ARCHIVE``: Guild has access to the seven day archive time for threads.
|
||||||
|
- ``THREE_DAY_THREAD_ARCHIVE``: Guild has access to the three day archive time for threads.
|
||||||
|
- ``TICKETED_EVENTS_ENABLED``: Guild has enabled ticketed events.
|
||||||
|
- ``VANITY_URL``: Guild can have a vanity invite URL (e.g. discord.gg/discord-api).
|
||||||
|
- ``VERIFIED``: Guild is a verified server.
|
||||||
|
- ``VIP_REGIONS``: Guild has VIP voice regions.
|
||||||
|
- ``WELCOME_SCREEN_ENABLED``: Guild has enabled the welcome screen.
|
||||||
|
|
||||||
premium_tier: :class:`int`
|
premium_tier: :class:`int`
|
||||||
The premium tier for this guild. Corresponds to "Nitro Server" in the official UI.
|
The premium tier for this guild. Corresponds to "Nitro Server" in the official UI.
|
||||||
@@ -227,6 +248,7 @@ class Guild(Hashable):
|
|||||||
'owner_id',
|
'owner_id',
|
||||||
'mfa_level',
|
'mfa_level',
|
||||||
'emojis',
|
'emojis',
|
||||||
|
'stickers',
|
||||||
'features',
|
'features',
|
||||||
'verification_level',
|
'verification_level',
|
||||||
'explicit_content_filter',
|
'explicit_content_filter',
|
||||||
@@ -259,11 +281,11 @@ class Guild(Hashable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
_PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = {
|
_PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = {
|
||||||
None: _GuildLimit(emoji=50, bitrate=96e3, filesize=8388608),
|
None: _GuildLimit(emoji=50, stickers=0, bitrate=96e3, filesize=8388608),
|
||||||
0: _GuildLimit(emoji=50, bitrate=96e3, filesize=8388608),
|
0: _GuildLimit(emoji=50, stickers=0, bitrate=96e3, filesize=8388608),
|
||||||
1: _GuildLimit(emoji=100, bitrate=128e3, filesize=8388608),
|
1: _GuildLimit(emoji=100, stickers=15, bitrate=128e3, filesize=8388608),
|
||||||
2: _GuildLimit(emoji=150, bitrate=256e3, filesize=52428800),
|
2: _GuildLimit(emoji=150, stickers=30, bitrate=256e3, filesize=52428800),
|
||||||
3: _GuildLimit(emoji=250, bitrate=384e3, filesize=104857600),
|
3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104857600),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *, data: GuildPayload, state: ConnectionState):
|
def __init__(self, *, data: GuildPayload, state: ConnectionState):
|
||||||
@@ -287,7 +309,7 @@ class Guild(Hashable):
|
|||||||
self._members[member.id] = member
|
self._members[member.id] = member
|
||||||
|
|
||||||
def _store_thread(self, payload: ThreadPayload, /) -> Thread:
|
def _store_thread(self, payload: ThreadPayload, /) -> Thread:
|
||||||
thread = Thread(guild=self, data=payload)
|
thread = Thread(guild=self, state=self._state, data=payload)
|
||||||
self._threads[thread.id] = thread
|
self._threads[thread.id] = thread
|
||||||
return thread
|
return thread
|
||||||
|
|
||||||
@@ -388,7 +410,9 @@ class Guild(Hashable):
|
|||||||
self.name: str = guild.get('name')
|
self.name: str = guild.get('name')
|
||||||
self.region: VoiceRegion = try_enum(VoiceRegion, guild.get('region'))
|
self.region: VoiceRegion = try_enum(VoiceRegion, guild.get('region'))
|
||||||
self.verification_level: VerificationLevel = try_enum(VerificationLevel, guild.get('verification_level'))
|
self.verification_level: VerificationLevel = try_enum(VerificationLevel, guild.get('verification_level'))
|
||||||
self.default_notifications: NotificationLevel = try_enum(NotificationLevel, guild.get('default_message_notifications'))
|
self.default_notifications: NotificationLevel = try_enum(
|
||||||
|
NotificationLevel, guild.get('default_message_notifications')
|
||||||
|
)
|
||||||
self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0))
|
self.explicit_content_filter: ContentFilter = try_enum(ContentFilter, guild.get('explicit_content_filter', 0))
|
||||||
self.afk_timeout: int = guild.get('afk_timeout')
|
self.afk_timeout: int = guild.get('afk_timeout')
|
||||||
self._icon: Optional[str] = guild.get('icon')
|
self._icon: Optional[str] = guild.get('icon')
|
||||||
@@ -403,7 +427,10 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
self.mfa_level: MFALevel = guild.get('mfa_level')
|
self.mfa_level: MFALevel = guild.get('mfa_level')
|
||||||
self.emojis: Tuple[Emoji, ...] = tuple(map(lambda d: state.store_emoji(self, d), guild.get('emojis', [])))
|
self.emojis: Tuple[Emoji, ...] = tuple(map(lambda d: state.store_emoji(self, d), guild.get('emojis', [])))
|
||||||
self.features: List[str] = guild.get('features', [])
|
self.stickers: Tuple[GuildSticker, ...] = tuple(
|
||||||
|
map(lambda d: state.store_sticker(self, d), guild.get('stickers', []))
|
||||||
|
)
|
||||||
|
self.features: List[GuildFeature] = guild.get('features', [])
|
||||||
self._splash: Optional[str] = guild.get('splash')
|
self._splash: Optional[str] = guild.get('splash')
|
||||||
self._system_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'system_channel_id')
|
self._system_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'system_channel_id')
|
||||||
self.description: Optional[str] = guild.get('description')
|
self.description: Optional[str] = guild.get('description')
|
||||||
@@ -452,19 +479,19 @@ class Guild(Hashable):
|
|||||||
user_id = int(presence['user']['id'])
|
user_id = int(presence['user']['id'])
|
||||||
member = self.get_member(user_id)
|
member = self.get_member(user_id)
|
||||||
if member is not None:
|
if member is not None:
|
||||||
member._presence_update(presence, empty_tuple)
|
member._presence_update(presence, empty_tuple) # type: ignore
|
||||||
|
|
||||||
if 'channels' in data:
|
if 'channels' in data:
|
||||||
channels = data['channels']
|
channels = data['channels']
|
||||||
for c in channels:
|
for c in channels:
|
||||||
factory, ch_type = _guild_channel_factory(c['type'])
|
factory, ch_type = _guild_channel_factory(c['type'])
|
||||||
if factory:
|
if factory:
|
||||||
self._add_channel(factory(guild=self, data=c, state=self._state))
|
self._add_channel(factory(guild=self, data=c, state=self._state)) # type: ignore
|
||||||
|
|
||||||
if 'threads' in data:
|
if 'threads' in data:
|
||||||
threads = data['threads']
|
threads = data['threads']
|
||||||
for thread in threads:
|
for thread in threads:
|
||||||
self._add_thread(Thread(guild=self, data=thread))
|
self._add_thread(Thread(guild=self, state=self._state, data=thread))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def channels(self) -> List[GuildChannel]:
|
def channels(self) -> List[GuildChannel]:
|
||||||
@@ -584,6 +611,29 @@ class Guild(Hashable):
|
|||||||
channels.sort(key=lambda c: (c._sorting_bucket, c.position, c.id))
|
channels.sort(key=lambda c: (c._sorting_bucket, c.position, c.id))
|
||||||
return as_list
|
return as_list
|
||||||
|
|
||||||
|
def _resolve_channel(self, id: Optional[int], /) -> Optional[Union[GuildChannel, Thread]]:
|
||||||
|
if id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return self._channels.get(id) or self._threads.get(id)
|
||||||
|
|
||||||
|
def get_channel_or_thread(self, channel_id: int, /) -> Optional[Union[Thread, GuildChannel]]:
|
||||||
|
"""Returns a channel or thread with the given ID.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
channel_id: :class:`int`
|
||||||
|
The ID to search for.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Optional[Union[:class:`Thread`, :class:`.abc.GuildChannel`]]
|
||||||
|
The returned channel or thread or ``None`` if not found.
|
||||||
|
"""
|
||||||
|
return self._channels.get(channel_id) or self._threads.get(channel_id)
|
||||||
|
|
||||||
def get_channel(self, channel_id: int, /) -> Optional[GuildChannel]:
|
def get_channel(self, channel_id: int, /) -> Optional[GuildChannel]:
|
||||||
"""Returns a channel with the given ID.
|
"""Returns a channel with the given ID.
|
||||||
|
|
||||||
@@ -665,6 +715,15 @@ class Guild(Hashable):
|
|||||||
more_emoji = 200 if 'MORE_EMOJI' in self.features else 50
|
more_emoji = 200 if 'MORE_EMOJI' in self.features else 50
|
||||||
return max(more_emoji, self._PREMIUM_GUILD_LIMITS[self.premium_tier].emoji)
|
return max(more_emoji, self._PREMIUM_GUILD_LIMITS[self.premium_tier].emoji)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sticker_limit(self) -> int:
|
||||||
|
""":class:`int`: The maximum number of sticker slots this guild has.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
more_stickers = 60 if 'MORE_STICKERS' in self.features else 0
|
||||||
|
return max(more_stickers, self._PREMIUM_GUILD_LIMITS[self.premium_tier].stickers)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bitrate_limit(self) -> float:
|
def bitrate_limit(self) -> float:
|
||||||
""":class:`float`: The maximum bitrate for voice channels this guild can have."""
|
""":class:`float`: The maximum bitrate for voice channels this guild can have."""
|
||||||
@@ -681,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
|
||||||
@@ -1301,8 +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.
|
||||||
@@ -1316,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`
|
||||||
@@ -1389,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
|
||||||
@@ -1501,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|
|
||||||
@@ -1538,6 +1620,35 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
return [convert(d) for d in data]
|
return [convert(d) for d in data]
|
||||||
|
|
||||||
|
async def active_threads(self) -> List[Thread]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Returns a list of active :class:`Thread` that the client can access.
|
||||||
|
|
||||||
|
This includes both private and public threads.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
HTTPException
|
||||||
|
The request to get the active threads failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
List[:class:`Thread`]
|
||||||
|
The active threads
|
||||||
|
"""
|
||||||
|
data = await self._state.http.get_active_threads(self.id)
|
||||||
|
threads = [Thread(guild=self, state=self._state, data=d) for d in data.get('threads', [])]
|
||||||
|
thread_lookup: Dict[int, Thread] = {thread.id: thread for thread in threads}
|
||||||
|
for member in data.get('members', []):
|
||||||
|
thread = thread_lookup.get(int(member['id']))
|
||||||
|
if thread is not None:
|
||||||
|
thread._add_member(ThreadMember(parent=thread, data=member))
|
||||||
|
|
||||||
|
return threads
|
||||||
|
|
||||||
# TODO: Remove Optional typing here when async iterators are refactored
|
# TODO: Remove Optional typing here when async iterators are refactored
|
||||||
def fetch_members(self, *, limit: int = 1000, after: Optional[SnowflakeTime] = None) -> MemberIterator:
|
def fetch_members(self, *, limit: int = 1000, after: Optional[SnowflakeTime] = None) -> MemberIterator:
|
||||||
"""Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. In order to use this,
|
"""Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. In order to use this,
|
||||||
@@ -1651,14 +1762,14 @@ class Guild(Hashable):
|
|||||||
data: BanPayload = await self._state.http.get_ban(user.id, self.id)
|
data: BanPayload = await self._state.http.get_ban(user.id, self.id)
|
||||||
return BanEntry(user=User(state=self._state, data=data['user']), reason=data['reason'])
|
return BanEntry(user=User(state=self._state, data=data['user']), reason=data['reason'])
|
||||||
|
|
||||||
async def fetch_channel(self, channel_id: int, /) -> GuildChannel:
|
async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, Thread]:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Retrieves a :class:`.abc.GuildChannel` with the specified ID.
|
Retrieves a :class:`.abc.GuildChannel` or :class:`.Thread` with the specified ID.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This method is an API call. For general usage, consider :meth:`get_channel` instead.
|
This method is an API call. For general usage, consider :meth:`get_channel_or_thread` instead.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
@@ -1677,12 +1788,12 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
:class:`.abc.GuildChannel`
|
Union[:class:`.abc.GuildChannel`, :class:`.Thread`]
|
||||||
The channel from the ID.
|
The channel from the ID.
|
||||||
"""
|
"""
|
||||||
data = await self._state.http.get_channel(channel_id)
|
data = await self._state.http.get_channel(channel_id)
|
||||||
|
|
||||||
factory, ch_type = _guild_channel_factory(data['type'])
|
factory, ch_type = _threaded_guild_channel_factory(data['type'])
|
||||||
if factory is None:
|
if factory is None:
|
||||||
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(data))
|
raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(data))
|
||||||
|
|
||||||
@@ -1995,6 +2106,150 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
return [convert(d) for d in data]
|
return [convert(d) for d in data]
|
||||||
|
|
||||||
|
async def fetch_stickers(self) -> List[GuildSticker]:
|
||||||
|
r"""|coro|
|
||||||
|
|
||||||
|
Retrieves a list of all :class:`Sticker`\s for the guild.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This method is an API call. For general usage, consider :attr:`stickers` instead.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
---------
|
||||||
|
HTTPException
|
||||||
|
An error occurred fetching the stickers.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
List[:class:`GuildSticker`]
|
||||||
|
The retrieved stickers.
|
||||||
|
"""
|
||||||
|
data = await self._state.http.get_all_guild_stickers(self.id)
|
||||||
|
return [GuildSticker(state=self._state, data=d) for d in data]
|
||||||
|
|
||||||
|
async def fetch_sticker(self, sticker_id: int, /) -> GuildSticker:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves a custom :class:`Sticker` from the guild.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This method is an API call.
|
||||||
|
For general usage, consider iterating over :attr:`stickers` instead.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-------------
|
||||||
|
sticker_id: :class:`int`
|
||||||
|
The sticker's ID.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
---------
|
||||||
|
NotFound
|
||||||
|
The sticker requested could not be found.
|
||||||
|
HTTPException
|
||||||
|
An error occurred fetching the sticker.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`GuildSticker`
|
||||||
|
The retrieved sticker.
|
||||||
|
"""
|
||||||
|
data = await self._state.http.get_guild_sticker(self.id, sticker_id)
|
||||||
|
return GuildSticker(state=self._state, data=data)
|
||||||
|
|
||||||
|
async def create_sticker(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
emoji: str,
|
||||||
|
file: File,
|
||||||
|
reason: Optional[str] = None,
|
||||||
|
) -> GuildSticker:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Creates a :class:`Sticker` for the guild.
|
||||||
|
|
||||||
|
You must have :attr:`~Permissions.manage_emojis_and_stickers` permission to
|
||||||
|
do this.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The sticker name. Must be at least 2 characters.
|
||||||
|
description: Optional[:class:`str`]
|
||||||
|
The sticker's description. Can be ``None``.
|
||||||
|
emoji: :class:`str`
|
||||||
|
The name of a unicode emoji that represents the sticker's expression.
|
||||||
|
file: :class:`File`
|
||||||
|
The file of the sticker to upload.
|
||||||
|
reason: :class:`str`
|
||||||
|
The reason for creating this sticker. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You are not allowed to create stickers.
|
||||||
|
HTTPException
|
||||||
|
An error occurred creating a sticker.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`GuildSticker`
|
||||||
|
The created sticker.
|
||||||
|
"""
|
||||||
|
payload = {
|
||||||
|
'name': name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if description:
|
||||||
|
payload['description'] = description
|
||||||
|
|
||||||
|
try:
|
||||||
|
emoji = unicodedata.name(emoji)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
emoji = emoji.replace(' ', '_')
|
||||||
|
|
||||||
|
payload['tags'] = emoji
|
||||||
|
|
||||||
|
data = await self._state.http.create_guild_sticker(self.id, payload, file, reason)
|
||||||
|
return self._state.store_sticker(self, data)
|
||||||
|
|
||||||
|
async def delete_sticker(self, sticker: Snowflake, *, reason: Optional[str] = None) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the custom :class:`Sticker` from the guild.
|
||||||
|
|
||||||
|
You must have :attr:`~Permissions.manage_emojis_and_stickers` permission to
|
||||||
|
do this.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
sticker: :class:`abc.Snowflake`
|
||||||
|
The sticker you are deleting.
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
|
The reason for deleting this sticker. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You are not allowed to delete stickers.
|
||||||
|
HTTPException
|
||||||
|
An error occurred deleting the sticker.
|
||||||
|
"""
|
||||||
|
await self._state.http.delete_guild_sticker(self.id, sticker.id, reason)
|
||||||
|
|
||||||
async def fetch_emojis(self) -> List[Emoji]:
|
async def fetch_emojis(self) -> List[Emoji]:
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
@@ -2157,7 +2412,7 @@ class Guild(Hashable):
|
|||||||
permissions: Permissions = ...,
|
permissions: Permissions = ...,
|
||||||
colour: Union[Colour, int] = ...,
|
colour: Union[Colour, int] = ...,
|
||||||
hoist: bool = ...,
|
hoist: bool = ...,
|
||||||
mentionable: str = ...,
|
mentionable: bool = ...,
|
||||||
) -> Role:
|
) -> Role:
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -2170,7 +2425,7 @@ class Guild(Hashable):
|
|||||||
permissions: Permissions = ...,
|
permissions: Permissions = ...,
|
||||||
color: Union[Colour, int] = ...,
|
color: Union[Colour, int] = ...,
|
||||||
hoist: bool = ...,
|
hoist: bool = ...,
|
||||||
mentionable: str = ...,
|
mentionable: bool = ...,
|
||||||
) -> Role:
|
) -> Role:
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -2182,10 +2437,9 @@ class Guild(Hashable):
|
|||||||
color: Union[Colour, int] = MISSING,
|
color: Union[Colour, int] = MISSING,
|
||||||
colour: Union[Colour, int] = MISSING,
|
colour: Union[Colour, int] = MISSING,
|
||||||
hoist: bool = MISSING,
|
hoist: bool = MISSING,
|
||||||
mentionable: str = MISSING,
|
mentionable: bool = MISSING,
|
||||||
reason: Optional[str] = None,
|
reason: Optional[str] = None,
|
||||||
) -> Role:
|
) -> Role:
|
||||||
...
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a :class:`Role` for the guild.
|
Creates a :class:`Role` for the guild.
|
||||||
@@ -2407,7 +2661,7 @@ class Guild(Hashable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.unban(user.id, self.id, reason=reason)
|
await self._state.http.unban(user.id, self.id, reason=reason)
|
||||||
|
|
||||||
async def vanity_invite(self) -> Invite:
|
async def vanity_invite(self) -> Optional[Invite]:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Returns the guild's special vanity invite.
|
Returns the guild's special vanity invite.
|
||||||
@@ -2426,12 +2680,15 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
:class:`Invite`
|
Optional[:class:`Invite`]
|
||||||
The special vanity invite.
|
The special vanity invite. If ``None`` then the guild does not
|
||||||
|
have a vanity invite set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# we start with { code: abc }
|
# we start with { code: abc }
|
||||||
payload = await self._state.http.get_vanity_code(self.id)
|
payload = await self._state.http.get_vanity_code(self.id)
|
||||||
|
if not payload['code']:
|
||||||
|
return None
|
||||||
|
|
||||||
# get the vanity URL channel since default channels aren't
|
# get the vanity URL channel since default channels aren't
|
||||||
# reliable or a thing anymore
|
# reliable or a thing anymore
|
||||||
@@ -2442,6 +2699,7 @@ class Guild(Hashable):
|
|||||||
payload['temporary'] = False
|
payload['temporary'] = False
|
||||||
payload['max_uses'] = 0
|
payload['max_uses'] = 0
|
||||||
payload['max_age'] = 0
|
payload['max_age'] = 0
|
||||||
|
payload['uses'] = payload.get('uses', 0)
|
||||||
return Invite(state=self._state, data=payload, guild=self, channel=channel)
|
return Invite(state=self._state, data=payload, guild=self, channel=channel)
|
||||||
|
|
||||||
# TODO: use MISSING when async iterators get refactored
|
# TODO: use MISSING when async iterators get refactored
|
||||||
|
|||||||
327
discord/http.py
327
discord/http.py
@@ -33,7 +33,6 @@ from typing import (
|
|||||||
ClassVar,
|
ClassVar,
|
||||||
Coroutine,
|
Coroutine,
|
||||||
Dict,
|
Dict,
|
||||||
Final,
|
|
||||||
Iterable,
|
Iterable,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
@@ -42,17 +41,19 @@ from typing import (
|
|||||||
Tuple,
|
Tuple,
|
||||||
Type,
|
Type,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
from urllib.parse import quote as _uriquote
|
from urllib.parse import quote as _uriquote
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .errors import HTTPException, Forbidden, NotFound, LoginFailure, DiscordServerError, GatewayNotFound
|
from .errors import HTTPException, Forbidden, NotFound, LoginFailure, DiscordServerError, GatewayNotFound, InvalidArgument
|
||||||
from .gateway import DiscordClientWebSocketResponse
|
from .gateway import DiscordClientWebSocketResponse
|
||||||
from . import __version__, utils
|
from . import __version__, utils
|
||||||
|
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
|
||||||
@@ -82,6 +83,7 @@ if TYPE_CHECKING:
|
|||||||
widget,
|
widget,
|
||||||
threads,
|
threads,
|
||||||
voice,
|
voice,
|
||||||
|
sticker,
|
||||||
)
|
)
|
||||||
from .types.snowflake import Snowflake, SnowflakeList
|
from .types.snowflake import Snowflake, SnowflakeList
|
||||||
|
|
||||||
@@ -93,11 +95,11 @@ if TYPE_CHECKING:
|
|||||||
Response = Coroutine[Any, Any, T]
|
Response = Coroutine[Any, Any, T]
|
||||||
|
|
||||||
|
|
||||||
async def json_or_text(response: aiohttp.ClientResponse) -> Any:
|
async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]:
|
||||||
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 json.loads(text)
|
return utils._from_json(text)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Thanks Cloudflare
|
# Thanks Cloudflare
|
||||||
pass
|
pass
|
||||||
@@ -139,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],
|
||||||
@@ -156,9 +159,6 @@ aiohttp.hdrs.WEBSOCKET = 'websocket' #type: ignore
|
|||||||
class HTTPClient:
|
class HTTPClient:
|
||||||
"""Represents an HTTP client sending HTTP requests to the Discord API."""
|
"""Represents an HTTP client sending HTTP requests to the Discord API."""
|
||||||
|
|
||||||
SUCCESS_LOG: Final[ClassVar[str]] = '{method} {url} has received {text}'
|
|
||||||
REQUEST_LOG: Final[ClassVar[str]] = '{method} {url} with {json} has returned {status}'
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
connector: Optional[aiohttp.BaseConnector] = None,
|
connector: Optional[aiohttp.BaseConnector] = None,
|
||||||
@@ -166,11 +166,11 @@ 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
|
||||||
self.__session: Optional[aiohttp.ClientSession] = None # filled in static_login
|
self.__session: aiohttp.ClientSession = MISSING # filled in static_login
|
||||||
self._locks: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
|
self._locks: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
|
||||||
self._global_over: asyncio.Event = asyncio.Event()
|
self._global_over: asyncio.Event = asyncio.Event()
|
||||||
self._global_over.set()
|
self._global_over.set()
|
||||||
@@ -210,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
|
||||||
@@ -223,7 +223,7 @@ class HTTPClient:
|
|||||||
self._locks[bucket] = lock
|
self._locks[bucket] = lock
|
||||||
|
|
||||||
# header creation
|
# header creation
|
||||||
headers = {
|
headers: Dict[str, str] = {
|
||||||
'User-Agent': self.user_agent,
|
'User-Agent': self.user_agent,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,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')
|
||||||
@@ -254,6 +254,8 @@ class HTTPClient:
|
|||||||
# wait until the global lock is complete
|
# wait until the global lock is complete
|
||||||
await self._global_over.wait()
|
await self._global_over.wait()
|
||||||
|
|
||||||
|
response: Optional[aiohttp.ClientResponse] = None
|
||||||
|
data: Optional[Union[Dict[str, Any], str]] = None
|
||||||
await lock.acquire()
|
await lock.acquire()
|
||||||
with MaybeUnlock(lock) as maybe_lock:
|
with MaybeUnlock(lock) as maybe_lock:
|
||||||
for tries in range(5):
|
for tries in range(5):
|
||||||
@@ -268,69 +270,69 @@ class HTTPClient:
|
|||||||
kwargs['data'] = form_data
|
kwargs['data'] = form_data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with self.__session.request(method, url, **kwargs) as r:
|
async with self.__session.request(method, url, **kwargs) as response:
|
||||||
log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
|
_log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), 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(r)
|
data = await json_or_text(response)
|
||||||
|
|
||||||
# check if we have rate limit header information
|
# check if we have rate limit header information
|
||||||
remaining = r.headers.get('X-Ratelimit-Remaining')
|
remaining = response.headers.get('X-Ratelimit-Remaining')
|
||||||
if remaining == '0' and r.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(r, 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 > r.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
|
||||||
if r.status == 429:
|
if response.status == 429:
|
||||||
if not r.headers.get('Via'):
|
if not response.headers.get('Via') or isinstance(data, str):
|
||||||
# Banned by Cloudflare more than likely.
|
# Banned by Cloudflare more than likely.
|
||||||
raise HTTPException(r, data)
|
raise HTTPException(response, data)
|
||||||
|
|
||||||
fmt = 'We are being rate limited. Retrying in %.2f seconds. Handled under the bucket "%s"'
|
fmt = 'We are being rate limited. Retrying in %.2f seconds. Handled under the bucket "%s"'
|
||||||
|
|
||||||
# sleep a bit
|
# sleep a bit
|
||||||
retry_after: float = data['retry_after'] # type: ignore
|
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
|
||||||
|
|
||||||
# we've received a 500 or 502, unconditional retry
|
# we've received a 500, 502, or 504, unconditional retry
|
||||||
if r.status in {500, 502}:
|
if response.status in {500, 502, 504}:
|
||||||
await asyncio.sleep(1 + tries * 2)
|
await asyncio.sleep(1 + tries * 2)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# the usual error cases
|
# the usual error cases
|
||||||
if r.status == 403:
|
if response.status == 403:
|
||||||
raise Forbidden(r, data)
|
raise Forbidden(response, data)
|
||||||
elif r.status == 404:
|
elif response.status == 404:
|
||||||
raise NotFound(r, data)
|
raise NotFound(response, data)
|
||||||
elif r.status == 503:
|
elif response.status >= 500:
|
||||||
raise DiscordServerError(r, data)
|
raise DiscordServerError(response, data)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(r, data)
|
raise HTTPException(response, data)
|
||||||
|
|
||||||
# This is handling exceptions from the request
|
# This is handling exceptions from the request
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@@ -340,11 +342,14 @@ class HTTPClient:
|
|||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
# We've run out of retries, raise.
|
# We've run out of retries, raise.
|
||||||
if r.status >= 500:
|
if response.status >= 500:
|
||||||
raise DiscordServerError(r, data)
|
raise DiscordServerError(response, data)
|
||||||
|
|
||||||
raise HTTPException(r, data)
|
raise HTTPException(response, data)
|
||||||
|
|
||||||
|
raise RuntimeError('Unreachable code in HTTP handling')
|
||||||
|
|
||||||
async def get_from_cdn(self, url: str) -> bytes:
|
async def get_from_cdn(self, url: str) -> bytes:
|
||||||
async with self.__session.get(url) as resp:
|
async with self.__session.get(url) as resp:
|
||||||
@@ -375,7 +380,7 @@ class HTTPClient:
|
|||||||
data = await self.request(Route('GET', '/users/@me'))
|
data = await self.request(Route('GET', '/users/@me'))
|
||||||
except HTTPException as exc:
|
except HTTPException as exc:
|
||||||
self.token = old_token
|
self.token = old_token
|
||||||
if exc.response.status == 401:
|
if exc.status == 401:
|
||||||
raise LoginFailure('Improper token has been passed.') from exc
|
raise LoginFailure('Improper token has been passed.') from exc
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -408,13 +413,15 @@ 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,
|
||||||
|
embeds: Optional[List[embed.Embed]] = None,
|
||||||
nonce: Optional[str] = None,
|
nonce: Optional[str] = None,
|
||||||
allowed_mentions: bool = None,
|
allowed_mentions: Optional[message.AllowedMentions] = None,
|
||||||
message_reference: bool = None,
|
message_reference: Optional[message.MessageReference] = None,
|
||||||
|
stickers: Optional[List[sticker.StickerItem]] = None,
|
||||||
components: Optional[List[components.Component]] = None,
|
components: Optional[List[components.Component]] = None,
|
||||||
) -> Response[message.Message]:
|
) -> Response[message.Message]:
|
||||||
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
||||||
@@ -427,7 +434,10 @@ class HTTPClient:
|
|||||||
payload['tts'] = True
|
payload['tts'] = True
|
||||||
|
|
||||||
if embed:
|
if embed:
|
||||||
payload['embed'] = embed
|
payload['embeds'] = [embed]
|
||||||
|
|
||||||
|
if embeds:
|
||||||
|
payload['embeds'] = embeds
|
||||||
|
|
||||||
if nonce:
|
if nonce:
|
||||||
payload['nonce'] = nonce
|
payload['nonce'] = nonce
|
||||||
@@ -441,6 +451,9 @@ class HTTPClient:
|
|||||||
if components:
|
if components:
|
||||||
payload['components'] = components
|
payload['components'] = components
|
||||||
|
|
||||||
|
if stickers:
|
||||||
|
payload['sticker_ids'] = stickers
|
||||||
|
|
||||||
return self.request(r, json=payload)
|
return self.request(r, json=payload)
|
||||||
|
|
||||||
def send_typing(self, channel_id: Snowflake) -> Response[None]:
|
def send_typing(self, channel_id: Snowflake) -> Response[None]:
|
||||||
@@ -454,10 +467,11 @@ class HTTPClient:
|
|||||||
content: Optional[str] = None,
|
content: Optional[str] = None,
|
||||||
tts: bool = False,
|
tts: bool = False,
|
||||||
embed: Optional[embed.Embed] = None,
|
embed: Optional[embed.Embed] = None,
|
||||||
embeds: Iterable[Optional[embed.Embed]] = None,
|
embeds: Optional[Iterable[Optional[embed.Embed]]] = None,
|
||||||
nonce: Optional[str] = None,
|
nonce: Optional[str] = None,
|
||||||
allowed_mentions: Optional[message.AllowedMentions] = None,
|
allowed_mentions: Optional[message.AllowedMentions] = None,
|
||||||
message_reference: Optional[message.MessageReference] = None,
|
message_reference: Optional[message.MessageReference] = None,
|
||||||
|
stickers: Optional[List[sticker.StickerItem]] = None,
|
||||||
components: Optional[List[components.Component]] = None,
|
components: Optional[List[components.Component]] = None,
|
||||||
) -> Response[message.Message]:
|
) -> Response[message.Message]:
|
||||||
form = []
|
form = []
|
||||||
@@ -466,7 +480,7 @@ class HTTPClient:
|
|||||||
if content:
|
if content:
|
||||||
payload['content'] = content
|
payload['content'] = content
|
||||||
if embed:
|
if embed:
|
||||||
payload['embed'] = embed
|
payload['embeds'] = [embed]
|
||||||
if embeds:
|
if embeds:
|
||||||
payload['embeds'] = embeds
|
payload['embeds'] = embeds
|
||||||
if nonce:
|
if nonce:
|
||||||
@@ -477,8 +491,10 @@ class HTTPClient:
|
|||||||
payload['message_reference'] = message_reference
|
payload['message_reference'] = message_reference
|
||||||
if components:
|
if components:
|
||||||
payload['components'] = components
|
payload['components'] = components
|
||||||
|
if 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(
|
||||||
@@ -510,9 +526,11 @@ class HTTPClient:
|
|||||||
content: Optional[str] = None,
|
content: Optional[str] = None,
|
||||||
tts: bool = False,
|
tts: bool = False,
|
||||||
embed: Optional[embed.Embed] = None,
|
embed: Optional[embed.Embed] = None,
|
||||||
|
embeds: Optional[List[embed.Embed]] = None,
|
||||||
nonce: Optional[str] = None,
|
nonce: Optional[str] = None,
|
||||||
allowed_mentions: Optional[message.AllowedMentions] = None,
|
allowed_mentions: Optional[message.AllowedMentions] = None,
|
||||||
message_reference: Optional[message.MessageReference] = None,
|
message_reference: Optional[message.MessageReference] = None,
|
||||||
|
stickers: Optional[List[sticker.StickerItem]] = None,
|
||||||
components: Optional[List[components.Component]] = None,
|
components: Optional[List[components.Component]] = None,
|
||||||
) -> Response[message.Message]:
|
) -> Response[message.Message]:
|
||||||
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
|
||||||
@@ -522,17 +540,23 @@ class HTTPClient:
|
|||||||
content=content,
|
content=content,
|
||||||
tts=tts,
|
tts=tts,
|
||||||
embed=embed,
|
embed=embed,
|
||||||
|
embeds=embeds,
|
||||||
nonce=nonce,
|
nonce=nonce,
|
||||||
allowed_mentions=allowed_mentions,
|
allowed_mentions=allowed_mentions,
|
||||||
message_reference=message_reference,
|
message_reference=message_reference,
|
||||||
|
stickers=stickers,
|
||||||
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,
|
||||||
@@ -554,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}',
|
||||||
@@ -591,7 +617,7 @@ class HTTPClient:
|
|||||||
emoji=emoji,
|
emoji=emoji,
|
||||||
)
|
)
|
||||||
|
|
||||||
params = {
|
params: Dict[str, Any] = {
|
||||||
'limit': limit,
|
'limit': limit,
|
||||||
}
|
}
|
||||||
if after:
|
if after:
|
||||||
@@ -700,11 +726,7 @@ class HTTPClient:
|
|||||||
'delete_message_days': delete_message_days,
|
'delete_message_days': delete_message_days,
|
||||||
}
|
}
|
||||||
|
|
||||||
if reason:
|
return self.request(r, params=params, reason=reason)
|
||||||
# thanks aiohttp
|
|
||||||
r.url = f'{r.url}?reason={_uriquote(reason)}'
|
|
||||||
|
|
||||||
return self.request(r, params=params)
|
|
||||||
|
|
||||||
def unban(self, user_id: Snowflake, guild_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]:
|
def unban(self, user_id: Snowflake, guild_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]:
|
||||||
r = Route('DELETE', '/guilds/{guild_id}/bans/{user_id}', guild_id=guild_id, user_id=user_id)
|
r = Route('DELETE', '/guilds/{guild_id}/bans/{user_id}', guild_id=guild_id, user_id=user_id)
|
||||||
@@ -729,13 +751,7 @@ class HTTPClient:
|
|||||||
|
|
||||||
return self.request(r, json=payload, reason=reason)
|
return self.request(r, json=payload, reason=reason)
|
||||||
|
|
||||||
def edit_profile(self, username: Optional[str], avatar: Optional[bytes]) -> Response[user.User]:
|
def edit_profile(self, payload: Dict[str, Any]) -> Response[user.User]:
|
||||||
payload = {}
|
|
||||||
if avatar is not None:
|
|
||||||
payload['avatar'] = avatar
|
|
||||||
if username is not None:
|
|
||||||
payload['username'] = username
|
|
||||||
|
|
||||||
return self.request(Route('PATCH', '/users/@me'), json=payload)
|
return self.request(Route('PATCH', '/users/@me'), json=payload)
|
||||||
|
|
||||||
def change_my_nickname(
|
def change_my_nickname(
|
||||||
@@ -765,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)
|
||||||
|
|
||||||
@@ -780,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)
|
||||||
|
|
||||||
@@ -810,6 +826,8 @@ class HTTPClient:
|
|||||||
'archived',
|
'archived',
|
||||||
'auto_archive_duration',
|
'auto_archive_duration',
|
||||||
'locked',
|
'locked',
|
||||||
|
'invitable',
|
||||||
|
'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}
|
||||||
return self.request(r, reason=reason, json=payload)
|
return self.request(r, reason=reason, json=payload)
|
||||||
@@ -864,42 +882,44 @@ class HTTPClient:
|
|||||||
|
|
||||||
# Thread management
|
# Thread management
|
||||||
|
|
||||||
def start_public_thread(
|
def start_thread_with_message(
|
||||||
self,
|
self,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
message_id: Snowflake,
|
message_id: Snowflake,
|
||||||
*,
|
*,
|
||||||
name: str,
|
name: str,
|
||||||
auto_archive_duration: threads.ThreadArchiveDuration,
|
auto_archive_duration: threads.ThreadArchiveDuration,
|
||||||
type: threads.ThreadType,
|
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
route = Route(
|
route = Route(
|
||||||
'POST', '/channels/{channel_id}/messages/{message_id}/threads', channel_id=channel_id, message_id=message_id
|
'POST', '/channels/{channel_id}/messages/{message_id}/threads', channel_id=channel_id, message_id=message_id
|
||||||
)
|
)
|
||||||
return self.request(route, json=payload)
|
return self.request(route, json=payload, reason=reason)
|
||||||
|
|
||||||
def start_private_thread(
|
def start_thread_without_message(
|
||||||
self,
|
self,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
*,
|
*,
|
||||||
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,
|
||||||
) -> 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)
|
||||||
return self.request(route, json=payload)
|
return self.request(route, json=payload, reason=reason)
|
||||||
|
|
||||||
def join_thread(self, channel_id: Snowflake) -> Response[None]:
|
def join_thread(self, channel_id: Snowflake) -> Response[None]:
|
||||||
return self.request(Route('POST', '/channels/{channel_id}/thread-members/@me', channel_id=channel_id))
|
return self.request(Route('POST', '/channels/{channel_id}/thread-members/@me', channel_id=channel_id))
|
||||||
@@ -948,8 +968,8 @@ class HTTPClient:
|
|||||||
params['limit'] = limit
|
params['limit'] = limit
|
||||||
return self.request(route, params=params)
|
return self.request(route, params=params)
|
||||||
|
|
||||||
def get_active_threads(self, channel_id: Snowflake) -> Response[threads.ThreadPaginationPayload]:
|
def get_active_threads(self, guild_id: Snowflake) -> Response[threads.ThreadPaginationPayload]:
|
||||||
route = Route('GET', '/channels/{channel_id}/threads/active', channel_id=channel_id)
|
route = Route('GET', '/guilds/{guild_id}/threads/active', guild_id=guild_id)
|
||||||
return self.request(route)
|
return self.request(route)
|
||||||
|
|
||||||
def get_thread_members(self, channel_id: Snowflake) -> Response[List[threads.ThreadMember]]:
|
def get_thread_members(self, channel_id: Snowflake) -> Response[List[threads.ThreadMember]]:
|
||||||
@@ -1025,12 +1045,13 @@ class HTTPClient:
|
|||||||
def delete_guild(self, guild_id: Snowflake) -> Response[None]:
|
def delete_guild(self, guild_id: Snowflake) -> Response[None]:
|
||||||
return self.request(Route('DELETE', '/guilds/{guild_id}', guild_id=guild_id))
|
return self.request(Route('DELETE', '/guilds/{guild_id}', guild_id=guild_id))
|
||||||
|
|
||||||
def create_guild(self, name: str, region: str, icon: bytes) -> Response[guild.Guild]:
|
def create_guild(self, name: str, region: str, icon: Optional[str]) -> Response[guild.Guild]:
|
||||||
payload = {
|
payload = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'icon': icon,
|
|
||||||
'region': region,
|
'region': region,
|
||||||
}
|
}
|
||||||
|
if icon:
|
||||||
|
payload['icon'] = icon
|
||||||
|
|
||||||
return self.request(Route('POST', '/guilds'), json=payload)
|
return self.request(Route('POST', '/guilds'), json=payload)
|
||||||
|
|
||||||
@@ -1086,12 +1107,13 @@ class HTTPClient:
|
|||||||
def delete_template(self, guild_id: Snowflake, code: str) -> Response[None]:
|
def delete_template(self, guild_id: Snowflake, code: str) -> Response[None]:
|
||||||
return self.request(Route('DELETE', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code))
|
return self.request(Route('DELETE', '/guilds/{guild_id}/templates/{code}', guild_id=guild_id, code=code))
|
||||||
|
|
||||||
def create_from_template(self, code: str, name: str, region: str, icon: bytes) -> Response[guild.Guild]:
|
def create_from_template(self, code: str, name: str, region: str, icon: Optional[str]) -> Response[guild.Guild]:
|
||||||
payload = {
|
payload = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'icon': icon,
|
|
||||||
'region': region,
|
'region': region,
|
||||||
}
|
}
|
||||||
|
if icon:
|
||||||
|
payload['icon'] = icon
|
||||||
return self.request(Route('POST', '/guilds/templates/{code}', code=code), json=payload)
|
return self.request(Route('POST', '/guilds/templates/{code}', code=code), json=payload)
|
||||||
|
|
||||||
def get_bans(self, guild_id: Snowflake) -> Response[List[guild.Ban]]:
|
def get_bans(self, guild_id: Snowflake) -> Response[List[guild.Ban]]:
|
||||||
@@ -1110,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,
|
||||||
}
|
}
|
||||||
@@ -1120,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(
|
||||||
@@ -1155,6 +1179,71 @@ class HTTPClient:
|
|||||||
|
|
||||||
return self.request(Route('GET', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params)
|
return self.request(Route('GET', '/guilds/{guild_id}/prune', guild_id=guild_id), params=params)
|
||||||
|
|
||||||
|
def get_sticker(self, sticker_id: Snowflake) -> Response[sticker.Sticker]:
|
||||||
|
return self.request(Route('GET', '/stickers/{sticker_id}', sticker_id=sticker_id))
|
||||||
|
|
||||||
|
def list_premium_sticker_packs(self) -> Response[sticker.ListPremiumStickerPacks]:
|
||||||
|
return self.request(Route('GET', '/sticker-packs'))
|
||||||
|
|
||||||
|
def get_all_guild_stickers(self, guild_id: Snowflake) -> Response[List[sticker.GuildSticker]]:
|
||||||
|
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]:
|
||||||
|
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]:
|
||||||
|
initial_bytes = file.fp.read(16)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mime_type = utils._get_mime_type_for_image(initial_bytes)
|
||||||
|
except InvalidArgument:
|
||||||
|
if initial_bytes.startswith(b'{'):
|
||||||
|
mime_type = 'application/json'
|
||||||
|
else:
|
||||||
|
mime_type = 'application/octet-stream'
|
||||||
|
finally:
|
||||||
|
file.reset()
|
||||||
|
|
||||||
|
form: List[Dict[str, Any]] = [
|
||||||
|
{
|
||||||
|
'name': 'file',
|
||||||
|
'value': file.fp,
|
||||||
|
'filename': file.filename,
|
||||||
|
'content_type': mime_type,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for k, v in payload.items():
|
||||||
|
form.append(
|
||||||
|
{
|
||||||
|
'name': k,
|
||||||
|
'value': v,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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: 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: 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,
|
||||||
|
)
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
@@ -1228,12 +1317,14 @@ class HTTPClient:
|
|||||||
|
|
||||||
return self.request(r)
|
return self.request(r)
|
||||||
|
|
||||||
def delete_integration(self, guild_id: Snowflake, integration_id: Snowflake) -> 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
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.request(r)
|
return self.request(r, reason=reason)
|
||||||
|
|
||||||
def get_audit_logs(
|
def get_audit_logs(
|
||||||
self,
|
self,
|
||||||
@@ -1276,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 = {
|
||||||
@@ -1297,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),
|
||||||
@@ -1318,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}
|
||||||
@@ -1335,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]:
|
||||||
@@ -1352,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}',
|
||||||
@@ -1362,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}',
|
||||||
@@ -1387,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)
|
||||||
@@ -1405,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
|
||||||
@@ -1413,7 +1508,7 @@ class HTTPClient:
|
|||||||
def get_stage_instance(self, channel_id: Snowflake) -> Response[channel.StageInstance]:
|
def get_stage_instance(self, channel_id: Snowflake) -> Response[channel.StageInstance]:
|
||||||
return self.request(Route('GET', '/stage-instances/{channel_id}', channel_id=channel_id))
|
return self.request(Route('GET', '/stage-instances/{channel_id}', channel_id=channel_id))
|
||||||
|
|
||||||
def create_stage_instance(self, **payload) -> Response[channel.StageInstance]:
|
def create_stage_instance(self, *, reason: Optional[str], **payload: Any) -> Response[channel.StageInstance]:
|
||||||
valid_keys = (
|
valid_keys = (
|
||||||
'channel_id',
|
'channel_id',
|
||||||
'topic',
|
'topic',
|
||||||
@@ -1421,26 +1516,30 @@ 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('POST', '/stage-instances'), json=payload)
|
return self.request(Route('POST', '/stage-instances'), json=payload, reason=reason)
|
||||||
|
|
||||||
def edit_stage_instance(self, channel_id: Snowflake, **payload) -> Response[None]:
|
def edit_stage_instance(self, channel_id: Snowflake, *, reason: Optional[str] = None, **payload: Any) -> Response[None]:
|
||||||
valid_keys = (
|
valid_keys = (
|
||||||
'topic',
|
'topic',
|
||||||
'privacy_level',
|
'privacy_level',
|
||||||
)
|
)
|
||||||
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)
|
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) -> 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))
|
return self.request(Route('DELETE', '/stage-instances/{channel_id}', channel_id=channel_id), reason=reason)
|
||||||
|
|
||||||
# Application commands (global)
|
# Application commands (global)
|
||||||
|
|
||||||
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}',
|
||||||
@@ -1453,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,
|
||||||
@@ -1481,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',
|
||||||
@@ -1525,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,
|
||||||
@@ -1597,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),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1619,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',
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Optional, TYPE_CHECKING, overload, Type, Tuple
|
from typing import Any, Dict, Optional, TYPE_CHECKING, overload, Type, Tuple
|
||||||
from .utils import _get_as_snowflake, get, parse_time
|
from .utils import _get_as_snowflake, parse_time, MISSING
|
||||||
from .user import User
|
from .user import User
|
||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
from .enums import try_enum, ExpireBehaviour
|
from .enums import try_enum, ExpireBehaviour
|
||||||
@@ -59,7 +59,7 @@ class IntegrationAccount:
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
id: :class:`int`
|
id: :class:`str`
|
||||||
The account ID.
|
The account ID.
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The account name.
|
The account name.
|
||||||
@@ -68,8 +68,8 @@ class IntegrationAccount:
|
|||||||
__slots__ = ('id', 'name')
|
__slots__ = ('id', 'name')
|
||||||
|
|
||||||
def __init__(self, data: IntegrationAccountPayload) -> None:
|
def __init__(self, data: IntegrationAccountPayload) -> None:
|
||||||
self.id: Optional[int] = _get_as_snowflake(data, 'id')
|
self.id: str = data['id']
|
||||||
self.name: str = data.pop('name')
|
self.name: str = data['name']
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<IntegrationAccount id={self.id} name={self.name!r}>'
|
return f'<IntegrationAccount id={self.id} name={self.name!r}>'
|
||||||
@@ -127,7 +127,7 @@ class Integration:
|
|||||||
self.user = User(state=self._state, data=user) if user else None
|
self.user = User(state=self._state, data=user) if user else None
|
||||||
self.enabled: bool = data['enabled']
|
self.enabled: bool = data['enabled']
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self, *, reason: Optional[str] = None) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Deletes the integration.
|
Deletes the integration.
|
||||||
@@ -135,6 +135,13 @@ class Integration:
|
|||||||
You must have the :attr:`~Permissions.manage_guild` permission to
|
You must have the :attr:`~Permissions.manage_guild` permission to
|
||||||
do this.
|
do this.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
reason: :class:`str`
|
||||||
|
The reason the integration was deleted. Shows up on the audit log.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
Forbidden
|
Forbidden
|
||||||
@@ -142,7 +149,7 @@ class Integration:
|
|||||||
HTTPException
|
HTTPException
|
||||||
Deleting the integration failed.
|
Deleting the integration failed.
|
||||||
"""
|
"""
|
||||||
await self._state.http.delete_integration(self.guild.id, self.id)
|
await self._state.http.delete_integration(self.guild.id, self.id, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
class StreamIntegration(Integration):
|
class StreamIntegration(Integration):
|
||||||
@@ -181,7 +188,6 @@ class StreamIntegration(Integration):
|
|||||||
__slots__ = (
|
__slots__ = (
|
||||||
'revoked',
|
'revoked',
|
||||||
'expire_behaviour',
|
'expire_behaviour',
|
||||||
'expire_behavior',
|
|
||||||
'expire_grace_period',
|
'expire_grace_period',
|
||||||
'synced_at',
|
'synced_at',
|
||||||
'_role_id',
|
'_role_id',
|
||||||
@@ -196,31 +202,28 @@ class StreamIntegration(Integration):
|
|||||||
self.expire_behaviour: ExpireBehaviour = try_enum(ExpireBehaviour, data['expire_behavior'])
|
self.expire_behaviour: ExpireBehaviour = try_enum(ExpireBehaviour, data['expire_behavior'])
|
||||||
self.expire_grace_period: int = data['expire_grace_period']
|
self.expire_grace_period: int = data['expire_grace_period']
|
||||||
self.synced_at: datetime.datetime = parse_time(data['synced_at'])
|
self.synced_at: datetime.datetime = parse_time(data['synced_at'])
|
||||||
self._role_id: int = int(data['role_id'])
|
self._role_id: Optional[int] = _get_as_snowflake(data, 'role_id')
|
||||||
self.syncing: bool = data['syncing']
|
self.syncing: bool = data['syncing']
|
||||||
self.enable_emoticons: bool = data['enable_emoticons']
|
self.enable_emoticons: bool = data['enable_emoticons']
|
||||||
self.subscriber_count: int = data['subscriber_count']
|
self.subscriber_count: int = data['subscriber_count']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expire_behavior(self) -> ExpireBehaviour:
|
||||||
|
""":class:`ExpireBehaviour`: An alias for :attr:`expire_behaviour`."""
|
||||||
|
return self.expire_behaviour
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def role(self) -> Optional[Role]:
|
def role(self) -> Optional[Role]:
|
||||||
"""Optional[:class:`Role`] The role which the integration uses for subscribers."""
|
"""Optional[:class:`Role`] The role which the integration uses for subscribers."""
|
||||||
return self.guild.get_role(self._role_id)
|
return self.guild.get_role(self._role_id) # type: ignore
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(
|
async def edit(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
expire_behaviour: Optional[ExpireBehaviour] = ...,
|
expire_behaviour: ExpireBehaviour = MISSING,
|
||||||
expire_grace_period: Optional[int] = ...,
|
expire_grace_period: int = MISSING,
|
||||||
enable_emoticons: Optional[bool] = ...,
|
enable_emoticons: bool = MISSING,
|
||||||
) -> None:
|
) -> None:
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(self, **fields) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
async def edit(self, **fields) -> None:
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the integration.
|
Edits the integration.
|
||||||
@@ -246,35 +249,23 @@ class StreamIntegration(Integration):
|
|||||||
InvalidArgument
|
InvalidArgument
|
||||||
``expire_behaviour`` did not receive a :class:`ExpireBehaviour`.
|
``expire_behaviour`` did not receive a :class:`ExpireBehaviour`.
|
||||||
"""
|
"""
|
||||||
try:
|
payload: Dict[str, Any] = {}
|
||||||
expire_behaviour = fields['expire_behaviour']
|
if expire_behaviour is not MISSING:
|
||||||
except KeyError:
|
|
||||||
expire_behaviour = fields.get('expire_behavior', self.expire_behaviour)
|
|
||||||
|
|
||||||
if not isinstance(expire_behaviour, ExpireBehaviour):
|
if not isinstance(expire_behaviour, ExpireBehaviour):
|
||||||
raise InvalidArgument('expire_behaviour field must be of type ExpireBehaviour')
|
raise InvalidArgument('expire_behaviour field must be of type ExpireBehaviour')
|
||||||
|
|
||||||
expire_grace_period = fields.get('expire_grace_period', self.expire_grace_period)
|
payload['expire_behavior'] = expire_behaviour.value
|
||||||
|
|
||||||
payload = {
|
if expire_grace_period is not MISSING:
|
||||||
'expire_behavior': expire_behaviour.value,
|
payload['expire_grace_period'] = expire_grace_period
|
||||||
'expire_grace_period': expire_grace_period,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
if enable_emoticons is not MISSING:
|
||||||
enable_emoticons = fields['enable_emoticons']
|
|
||||||
except KeyError:
|
|
||||||
enable_emoticons = self.enable_emoticons
|
|
||||||
else:
|
|
||||||
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)
|
||||||
|
|
||||||
self.expire_behaviour = expire_behaviour
|
|
||||||
self.expire_behavior = self.expire_behaviour
|
|
||||||
self.expire_grace_period = expire_grace_period
|
|
||||||
self.enable_emoticons = enable_emoticons
|
|
||||||
|
|
||||||
async def sync(self) -> None:
|
async def sync(self) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
|
|||||||
@@ -25,33 +25,45 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from discord.types.interactions import InteractionResponse
|
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .enums import try_enum, InteractionType, InteractionResponseType
|
from .enums import try_enum, InteractionType, InteractionResponseType
|
||||||
|
from .errors import InteractionResponded, HTTPException, ClientException
|
||||||
|
from .channel import PartialMessageable, ChannelType
|
||||||
|
|
||||||
from .user import User
|
from .user import User
|
||||||
from .member import Member
|
from .member import Member
|
||||||
from .message import Message, Attachment
|
from .message import Message, Attachment
|
||||||
from .object import Object
|
from .object import Object
|
||||||
from .webhook.async_ import async_context, Webhook
|
from .permissions import Permissions
|
||||||
|
from .webhook.async_ import async_context, Webhook, handle_message_parameters
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Interaction',
|
'Interaction',
|
||||||
|
'InteractionMessage',
|
||||||
'InteractionResponse',
|
'InteractionResponse',
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .types.interactions import (
|
from .types.interactions import (
|
||||||
Interaction as InteractionPayload,
|
Interaction as InteractionPayload,
|
||||||
|
InteractionData,
|
||||||
)
|
)
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .abc import GuildChannel
|
|
||||||
from .state import ConnectionState
|
from .state import ConnectionState
|
||||||
|
from .file import File
|
||||||
|
from .mentions import AllowedMentions
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
from .embeds import Embed
|
from .embeds import Embed
|
||||||
from .ui.view import View
|
from .ui.view import View
|
||||||
|
from .channel import VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, PartialMessageable
|
||||||
|
from .threads import Thread
|
||||||
|
|
||||||
|
InteractionChannel = Union[
|
||||||
|
VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable
|
||||||
|
]
|
||||||
|
|
||||||
MISSING: Any = utils.MISSING
|
MISSING: Any = utils.MISSING
|
||||||
|
|
||||||
@@ -60,8 +72,7 @@ class Interaction:
|
|||||||
"""Represents a Discord interaction.
|
"""Represents a Discord interaction.
|
||||||
|
|
||||||
An interaction happens when a user does an action that needs to
|
An interaction happens when a user does an action that needs to
|
||||||
be notified. Current examples are slash commands but future examples
|
be notified. Current examples are slash commands and components.
|
||||||
include forms and buttons.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
@@ -84,6 +95,8 @@ class Interaction:
|
|||||||
token: :class:`str`
|
token: :class:`str`
|
||||||
The token to continue the interaction. These are valid
|
The token to continue the interaction. These are valid
|
||||||
for 15 minutes.
|
for 15 minutes.
|
||||||
|
data: :class:`dict`
|
||||||
|
The raw interaction data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__: Tuple[str, ...] = (
|
__slots__: Tuple[str, ...] = (
|
||||||
@@ -97,42 +110,50 @@ class Interaction:
|
|||||||
'user',
|
'user',
|
||||||
'token',
|
'token',
|
||||||
'version',
|
'version',
|
||||||
|
'_permissions',
|
||||||
'_state',
|
'_state',
|
||||||
'_session',
|
'_session',
|
||||||
|
'_original_message',
|
||||||
'_cs_response',
|
'_cs_response',
|
||||||
'_cs_followup',
|
'_cs_followup',
|
||||||
|
'_cs_channel',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *, data: InteractionPayload, state: ConnectionState):
|
def __init__(self, *, data: InteractionPayload, state: ConnectionState):
|
||||||
self._state = state
|
self._state: ConnectionState = state
|
||||||
self._session: ClientSession = state.http._HTTPClient__session
|
self._session: ClientSession = state.http._HTTPClient__session
|
||||||
|
self._original_message: Optional[InteractionMessage] = None
|
||||||
self._from_data(data)
|
self._from_data(data)
|
||||||
|
|
||||||
def _from_data(self, data: InteractionPayload):
|
def _from_data(self, data: InteractionPayload):
|
||||||
self.id = int(data['id'])
|
self.id: int = int(data['id'])
|
||||||
self.type = try_enum(InteractionType, data['type'])
|
self.type: InteractionType = try_enum(InteractionType, data['type'])
|
||||||
self.data = data.get('data')
|
self.data: Optional[InteractionData] = data.get('data')
|
||||||
self.token = data['token']
|
self.token: str = data['token']
|
||||||
self.version = data['version']
|
self.version: int = data['version']
|
||||||
self.channel_id = utils._get_as_snowflake(data, 'channel_id')
|
self.channel_id: Optional[int] = utils._get_as_snowflake(data, 'channel_id')
|
||||||
self.guild_id = utils._get_as_snowflake(data, 'guild_id')
|
self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id')
|
||||||
self.application_id = utils._get_as_snowflake(data, 'application_id')
|
self.application_id: int = int(data['application_id'])
|
||||||
|
|
||||||
channel = self.channel or Object(id=self.channel_id)
|
self.message: Optional[Message]
|
||||||
try:
|
try:
|
||||||
self.message = Message(state=self._state, channel=channel, data=data['message'])
|
self.message = Message(state=self._state, channel=self.channel, data=data['message']) # type: ignore
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.message = None
|
self.message = None
|
||||||
|
|
||||||
self.user: Optional[Union[User, Member]] = None
|
self.user: Optional[Union[User, Member]] = None
|
||||||
|
self._permissions: int = 0
|
||||||
|
|
||||||
# TODO: there's a potential data loss here
|
# TODO: there's a potential data loss here
|
||||||
if self.guild_id:
|
if self.guild_id:
|
||||||
guild = self.guild or Object(id=self.guild_id)
|
guild = self.guild or Object(id=self.guild_id)
|
||||||
try:
|
try:
|
||||||
self.user = Member(state=self._state, guild=guild, data=data['member'])
|
member = data['member'] # type: ignore
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
self.user = Member(state=self._state, guild=guild, data=member) # type: ignore
|
||||||
|
self._permissions = int(member.get('permissions', 0))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.user = User(state=self._state, data=data['user'])
|
self.user = User(state=self._state, data=data['user'])
|
||||||
@@ -144,19 +165,37 @@ class Interaction:
|
|||||||
"""Optional[:class:`Guild`]: The guild the interaction was sent from."""
|
"""Optional[:class:`Guild`]: The guild the interaction was sent from."""
|
||||||
return self._state and self._state._get_guild(self.guild_id)
|
return self._state and self._state._get_guild(self.guild_id)
|
||||||
|
|
||||||
@property
|
@utils.cached_slot_property('_cs_channel')
|
||||||
def channel(self) -> Optional[GuildChannel]:
|
def channel(self) -> Optional[InteractionChannel]:
|
||||||
"""Optional[:class:`abc.GuildChannel`]: The channel the interaction was sent from.
|
"""Optional[Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]]: The channel the interaction was sent from.
|
||||||
|
|
||||||
Note that due to a Discord limitation, DM channels are not resolved since there is
|
Note that due to a Discord limitation, DM channels are not resolved since there is
|
||||||
no data to complete them.
|
no data to complete them. These are :class:`PartialMessageable` instead.
|
||||||
"""
|
"""
|
||||||
guild = self.guild
|
guild = self.guild
|
||||||
return guild and guild.get_channel(self.channel_id)
|
channel = guild and guild._resolve_channel(self.channel_id)
|
||||||
|
if channel is None:
|
||||||
|
if self.channel_id is not None:
|
||||||
|
type = ChannelType.text if self.guild_id is not None else ChannelType.private
|
||||||
|
return PartialMessageable(state=self._state, id=self.channel_id, type=type)
|
||||||
|
return None
|
||||||
|
return channel
|
||||||
|
|
||||||
|
@property
|
||||||
|
def permissions(self) -> Permissions:
|
||||||
|
""":class:`Permissions`: The resolved permissions of the member in the channel, including overwrites.
|
||||||
|
|
||||||
|
In a non-guild context where this doesn't apply, an empty permissions object is returned.
|
||||||
|
"""
|
||||||
|
return Permissions(self._permissions)
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_response')
|
@utils.cached_slot_property('_cs_response')
|
||||||
def response(self) -> InteractionResponse:
|
def response(self) -> InteractionResponse:
|
||||||
""":class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction."""
|
""":class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction.
|
||||||
|
|
||||||
|
A response can only be done once. If secondary messages need to be sent, consider using :attr:`followup`
|
||||||
|
instead.
|
||||||
|
"""
|
||||||
return InteractionResponse(self)
|
return InteractionResponse(self)
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_followup')
|
@utils.cached_slot_property('_cs_followup')
|
||||||
@@ -169,6 +208,157 @@ class Interaction:
|
|||||||
}
|
}
|
||||||
return Webhook.from_state(data=payload, state=self._state)
|
return Webhook.from_state(data=payload, state=self._state)
|
||||||
|
|
||||||
|
async def original_message(self) -> InteractionMessage:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Fetches the original interaction response message associated with the interaction.
|
||||||
|
|
||||||
|
If the interaction response was :meth:`InteractionResponse.send_message` then this would
|
||||||
|
return the message that was sent using that response. Otherwise, this would return
|
||||||
|
the message that triggered the interaction.
|
||||||
|
|
||||||
|
Repeated calls to this will return a cached value.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Fetching the original response message failed.
|
||||||
|
ClientException
|
||||||
|
The channel for the message could not be resolved.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
InteractionMessage
|
||||||
|
The original interaction response message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._original_message is not None:
|
||||||
|
return self._original_message
|
||||||
|
|
||||||
|
# TODO: fix later to not raise?
|
||||||
|
channel = self.channel
|
||||||
|
if channel is None:
|
||||||
|
raise ClientException('Channel for message could not be resolved')
|
||||||
|
|
||||||
|
adapter = async_context.get()
|
||||||
|
data = await adapter.get_original_interaction_response(
|
||||||
|
application_id=self.application_id,
|
||||||
|
token=self.token,
|
||||||
|
session=self._session,
|
||||||
|
)
|
||||||
|
state = _InteractionMessageState(self, self._state)
|
||||||
|
message = InteractionMessage(state=state, channel=channel, data=data) # type: ignore
|
||||||
|
self._original_message = message
|
||||||
|
return message
|
||||||
|
|
||||||
|
async def edit_original_message(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
content: Optional[str] = MISSING,
|
||||||
|
embeds: List[Embed] = MISSING,
|
||||||
|
embed: Optional[Embed] = MISSING,
|
||||||
|
file: File = MISSING,
|
||||||
|
files: List[File] = MISSING,
|
||||||
|
view: Optional[View] = MISSING,
|
||||||
|
allowed_mentions: Optional[AllowedMentions] = None,
|
||||||
|
) -> InteractionMessage:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the original interaction response message.
|
||||||
|
|
||||||
|
This is a lower level interface to :meth:`InteractionMessage.edit` in case
|
||||||
|
you do not want to fetch the message and save an HTTP request.
|
||||||
|
|
||||||
|
This method is also the only way to edit the original message if
|
||||||
|
the message sent was ephemeral.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
content: Optional[:class:`str`]
|
||||||
|
The content to edit the message with or ``None`` to clear it.
|
||||||
|
embeds: List[:class:`Embed`]
|
||||||
|
A list of embeds to edit the message with.
|
||||||
|
embed: Optional[:class:`Embed`]
|
||||||
|
The embed to edit the message with. ``None`` suppresses the embeds.
|
||||||
|
This should not be mixed with the ``embeds`` parameter.
|
||||||
|
file: :class:`File`
|
||||||
|
The file to upload. This cannot be mixed with ``files`` parameter.
|
||||||
|
files: List[:class:`File`]
|
||||||
|
A list of files to send with the content. This cannot be mixed with the
|
||||||
|
``file`` parameter.
|
||||||
|
allowed_mentions: :class:`AllowedMentions`
|
||||||
|
Controls the mentions being processed in this message.
|
||||||
|
See :meth:`.abc.Messageable.send` for more information.
|
||||||
|
view: Optional[:class:`~discord.ui.View`]
|
||||||
|
The updated view to update this message with. If ``None`` is passed then
|
||||||
|
the view is removed.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the message failed.
|
||||||
|
Forbidden
|
||||||
|
Edited a message that is not yours.
|
||||||
|
TypeError
|
||||||
|
You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
|
||||||
|
ValueError
|
||||||
|
The length of ``embeds`` was invalid.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`InteractionMessage`
|
||||||
|
The newly edited message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
previous_mentions: Optional[AllowedMentions] = self._state.allowed_mentions
|
||||||
|
params = handle_message_parameters(
|
||||||
|
content=content,
|
||||||
|
file=file,
|
||||||
|
files=files,
|
||||||
|
embed=embed,
|
||||||
|
embeds=embeds,
|
||||||
|
view=view,
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
previous_allowed_mentions=previous_mentions,
|
||||||
|
)
|
||||||
|
adapter = async_context.get()
|
||||||
|
data = await adapter.edit_original_interaction_response(
|
||||||
|
self.application_id,
|
||||||
|
self.token,
|
||||||
|
session=self._session,
|
||||||
|
payload=params.payload,
|
||||||
|
multipart=params.multipart,
|
||||||
|
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():
|
||||||
|
self._state.store_view(view, message.id)
|
||||||
|
return message
|
||||||
|
|
||||||
|
async def delete_original_message(self) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the original interaction response message.
|
||||||
|
|
||||||
|
This is a lower level interface to :meth:`InteractionMessage.delete` in case
|
||||||
|
you do not want to fetch the message and save an HTTP request.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Deleting the message failed.
|
||||||
|
Forbidden
|
||||||
|
Deleted a message that is not yours.
|
||||||
|
"""
|
||||||
|
adapter = async_context.get()
|
||||||
|
await adapter.delete_original_interaction_response(
|
||||||
|
self.application_id,
|
||||||
|
self.token,
|
||||||
|
session=self._session,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InteractionResponse:
|
class InteractionResponse:
|
||||||
"""Represents a Discord interaction response.
|
"""Represents a Discord interaction response.
|
||||||
@@ -187,6 +377,13 @@ class InteractionResponse:
|
|||||||
self._parent: Interaction = parent
|
self._parent: Interaction = parent
|
||||||
self._responded: bool = False
|
self._responded: bool = False
|
||||||
|
|
||||||
|
def is_done(self) -> bool:
|
||||||
|
""":class:`bool`: Indicates whether an interaction response has been done before.
|
||||||
|
|
||||||
|
An interaction can only be responded to once.
|
||||||
|
"""
|
||||||
|
return self._responded
|
||||||
|
|
||||||
async def defer(self, *, ephemeral: bool = False) -> None:
|
async def defer(self, *, ephemeral: bool = False) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@@ -205,9 +402,11 @@ class InteractionResponse:
|
|||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
Deferring the interaction failed.
|
Deferring the interaction failed.
|
||||||
|
InteractionResponded
|
||||||
|
This interaction has already been responded to before.
|
||||||
"""
|
"""
|
||||||
if self._responded:
|
if self._responded:
|
||||||
return
|
raise InteractionResponded(self._parent)
|
||||||
|
|
||||||
defer_type: int = 0
|
defer_type: int = 0
|
||||||
data: Optional[Dict[str, Any]] = None
|
data: Optional[Dict[str, Any]] = None
|
||||||
@@ -237,9 +436,11 @@ class InteractionResponse:
|
|||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
Ponging the interaction failed.
|
Ponging the interaction failed.
|
||||||
|
InteractionResponded
|
||||||
|
This interaction has already been responded to before.
|
||||||
"""
|
"""
|
||||||
if self._responded:
|
if self._responded:
|
||||||
return
|
raise InteractionResponded(self._parent)
|
||||||
|
|
||||||
parent = self._parent
|
parent = self._parent
|
||||||
if parent.type is InteractionType.ping:
|
if parent.type is InteractionType.ping:
|
||||||
@@ -290,9 +491,11 @@ class InteractionResponse:
|
|||||||
You specified both ``embed`` and ``embeds``.
|
You specified both ``embed`` and ``embeds``.
|
||||||
ValueError
|
ValueError
|
||||||
The length of ``embeds`` was invalid.
|
The length of ``embeds`` was invalid.
|
||||||
|
InteractionResponded
|
||||||
|
This interaction has already been responded to before.
|
||||||
"""
|
"""
|
||||||
if self._responded:
|
if self._responded:
|
||||||
return
|
raise InteractionResponded(self._parent)
|
||||||
|
|
||||||
payload: Dict[str, Any] = {
|
payload: Dict[str, Any] = {
|
||||||
'tts': tts,
|
'tts': tts,
|
||||||
@@ -372,9 +575,11 @@ class InteractionResponse:
|
|||||||
Editing the message failed.
|
Editing the message failed.
|
||||||
TypeError
|
TypeError
|
||||||
You specified both ``embed`` and ``embeds``.
|
You specified both ``embed`` and ``embeds``.
|
||||||
|
InteractionResponded
|
||||||
|
This interaction has already been responded to before.
|
||||||
"""
|
"""
|
||||||
if self._responded:
|
if self._responded:
|
||||||
return
|
raise InteractionResponded(self._parent)
|
||||||
|
|
||||||
parent = self._parent
|
parent = self._parent
|
||||||
msg = parent.message
|
msg = parent.message
|
||||||
@@ -383,7 +588,6 @@ class InteractionResponse:
|
|||||||
if parent.type is not InteractionType.component:
|
if parent.type is not InteractionType.component:
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: embeds: List[Embed]?
|
|
||||||
payload = {}
|
payload = {}
|
||||||
if content is not MISSING:
|
if content is not MISSING:
|
||||||
if content is None:
|
if content is None:
|
||||||
@@ -426,3 +630,138 @@ class InteractionResponse:
|
|||||||
state.store_view(view, message_id)
|
state.store_view(view, message_id)
|
||||||
|
|
||||||
self._responded = True
|
self._responded = True
|
||||||
|
|
||||||
|
|
||||||
|
class _InteractionMessageState:
|
||||||
|
__slots__ = ('_parent', '_interaction')
|
||||||
|
|
||||||
|
def __init__(self, interaction: Interaction, parent: ConnectionState):
|
||||||
|
self._interaction: Interaction = interaction
|
||||||
|
self._parent: ConnectionState = parent
|
||||||
|
|
||||||
|
def _get_guild(self, guild_id):
|
||||||
|
return self._parent._get_guild(guild_id)
|
||||||
|
|
||||||
|
def store_user(self, data):
|
||||||
|
return self._parent.store_user(data)
|
||||||
|
|
||||||
|
def create_user(self, data):
|
||||||
|
return self._parent.create_user(data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def http(self):
|
||||||
|
return self._parent.http
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self._parent, attr)
|
||||||
|
|
||||||
|
|
||||||
|
class InteractionMessage(Message):
|
||||||
|
"""Represents the original interaction response message.
|
||||||
|
|
||||||
|
This allows you to edit or delete the message associated with
|
||||||
|
the interaction response. To retrieve this object see :meth:`Interaction.original_message`.
|
||||||
|
|
||||||
|
This inherits from :class:`discord.Message` with changes to
|
||||||
|
:meth:`edit` and :meth:`delete` to work.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
_state: _InteractionMessageState
|
||||||
|
|
||||||
|
async def edit(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = MISSING,
|
||||||
|
embeds: List[Embed] = MISSING,
|
||||||
|
embed: Optional[Embed] = MISSING,
|
||||||
|
file: File = MISSING,
|
||||||
|
files: List[File] = MISSING,
|
||||||
|
view: Optional[View] = MISSING,
|
||||||
|
allowed_mentions: Optional[AllowedMentions] = None,
|
||||||
|
) -> InteractionMessage:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the message.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
content: Optional[:class:`str`]
|
||||||
|
The content to edit the message with or ``None`` to clear it.
|
||||||
|
embeds: List[:class:`Embed`]
|
||||||
|
A list of embeds to edit the message with.
|
||||||
|
embed: Optional[:class:`Embed`]
|
||||||
|
The embed to edit the message with. ``None`` suppresses the embeds.
|
||||||
|
This should not be mixed with the ``embeds`` parameter.
|
||||||
|
file: :class:`File`
|
||||||
|
The file to upload. This cannot be mixed with ``files`` parameter.
|
||||||
|
files: List[:class:`File`]
|
||||||
|
A list of files to send with the content. This cannot be mixed with the
|
||||||
|
``file`` parameter.
|
||||||
|
allowed_mentions: :class:`AllowedMentions`
|
||||||
|
Controls the mentions being processed in this message.
|
||||||
|
See :meth:`.abc.Messageable.send` for more information.
|
||||||
|
view: Optional[:class:`~discord.ui.View`]
|
||||||
|
The updated view to update this message with. If ``None`` is passed then
|
||||||
|
the view is removed.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the message failed.
|
||||||
|
Forbidden
|
||||||
|
Edited a message that is not yours.
|
||||||
|
TypeError
|
||||||
|
You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
|
||||||
|
ValueError
|
||||||
|
The length of ``embeds`` was invalid.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
:class:`InteractionMessage`
|
||||||
|
The newly edited message.
|
||||||
|
"""
|
||||||
|
return await self._state._interaction.edit_original_message(
|
||||||
|
content=content,
|
||||||
|
embeds=embeds,
|
||||||
|
embed=embed,
|
||||||
|
file=file,
|
||||||
|
files=files,
|
||||||
|
view=view,
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def delete(self, *, delay: Optional[float] = None) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the message.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
delay: Optional[:class:`float`]
|
||||||
|
If provided, the number of seconds to wait before deleting the message.
|
||||||
|
The waiting is done in the background and deletion failures are ignored.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
Forbidden
|
||||||
|
You do not have proper permissions to delete the message.
|
||||||
|
NotFound
|
||||||
|
The message was deleted already.
|
||||||
|
HTTPException
|
||||||
|
Deleting the message failed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if delay is not None:
|
||||||
|
|
||||||
|
async def inner_call(delay: float = delay):
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
try:
|
||||||
|
await self._state._interaction.delete_original_message()
|
||||||
|
except HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
asyncio.create_task(inner_call())
|
||||||
|
else:
|
||||||
|
await self._state._interaction.delete_original_message()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
+------------------------------------+------------------------------------------------------------+
|
+------------------------------------+------------------------------------------------------------+
|
||||||
@@ -257,7 +258,7 @@ class Invite(Hashable):
|
|||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
max_age: :class:`int`
|
max_age: :class:`int`
|
||||||
How long the before the invite expires in seconds.
|
How long before the invite expires in seconds.
|
||||||
A value of ``0`` indicates that it doesn't expire.
|
A value of ``0`` indicates that it doesn't expire.
|
||||||
code: :class:`str`
|
code: :class:`str`
|
||||||
The URL fragment used for the invite.
|
The URL fragment used for the invite.
|
||||||
@@ -352,12 +353,12 @@ class Invite(Hashable):
|
|||||||
self.expires_at: Optional[datetime.datetime] = parse_time(expires_at) if expires_at else None
|
self.expires_at: Optional[datetime.datetime] = parse_time(expires_at) if expires_at else None
|
||||||
|
|
||||||
inviter_data = data.get('inviter')
|
inviter_data = data.get('inviter')
|
||||||
self.inviter: Optional[User] = None if inviter_data is None else self._state.store_user(inviter_data)
|
self.inviter: Optional[User] = None if inviter_data is None else self._state.create_user(inviter_data)
|
||||||
|
|
||||||
self.channel: Optional[InviteChannelType] = self._resolve_channel(data.get('channel'), channel)
|
self.channel: Optional[InviteChannelType] = self._resolve_channel(data.get('channel'), channel)
|
||||||
|
|
||||||
target_user_data = data.get('target_user')
|
target_user_data = data.get('target_user')
|
||||||
self.target_user: Optional[User] = None if target_user_data is None else self._state.store_user(target_user_data)
|
self.target_user: Optional[User] = None if target_user_data is None else self._state.create_user(target_user_data)
|
||||||
|
|
||||||
self.target_type: InviteTarget = try_enum(InviteTarget, data.get("target_type", 0))
|
self.target_type: InviteTarget = try_enum(InviteTarget, data.get("target_type", 0))
|
||||||
|
|
||||||
@@ -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} '
|
||||||
|
|||||||
@@ -750,4 +750,4 @@ class ArchivedThreadIterator(_AsyncIterator['Thread']):
|
|||||||
|
|
||||||
def create_thread(self, data: ThreadPayload) -> Thread:
|
def create_thread(self, data: ThreadPayload) -> Thread:
|
||||||
from .threads import Thread
|
from .threads import Thread
|
||||||
return Thread(guild=self.guild, data=data)
|
return Thread(guild=self.guild, state=self.guild._state, data=data)
|
||||||
|
|||||||
@@ -29,13 +29,15 @@ import inspect
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
import sys
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import List, Literal, Optional, TYPE_CHECKING, Union, overload
|
from typing import Any, Dict, List, Literal, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union, overload
|
||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .user import BaseUser, User
|
from .asset import Asset
|
||||||
from .activity import create_activity
|
from .utils import MISSING
|
||||||
|
from .user import BaseUser, User, _UserTag
|
||||||
|
from .activity import create_activity, ActivityTypes
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .enums import Status, try_enum
|
from .enums import Status, try_enum
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
@@ -47,11 +49,26 @@ __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 .types.activity import PartialPresenceUpdate
|
||||||
|
from .types.member import (
|
||||||
|
MemberWithUser as MemberWithUserPayload,
|
||||||
|
Member as MemberPayload,
|
||||||
|
UserWithMember as UserWithMemberPayload,
|
||||||
|
)
|
||||||
|
from .types.user import User as UserPayload
|
||||||
from .abc import Snowflake
|
from .abc import Snowflake
|
||||||
|
from .state import ConnectionState
|
||||||
|
from .message import Message
|
||||||
|
from .role import Role
|
||||||
|
from .types.voice import VoiceState as VoiceStatePayload
|
||||||
|
|
||||||
VocalGuildChannel = Union[VoiceChannel, StageChannel]
|
VocalGuildChannel = Union[VoiceChannel, StageChannel]
|
||||||
|
|
||||||
|
|
||||||
class VoiceState:
|
class VoiceState:
|
||||||
"""Represents a Discord user's voice state.
|
"""Represents a Discord user's voice state.
|
||||||
|
|
||||||
@@ -95,38 +112,49 @@ class VoiceState:
|
|||||||
is not currently in a voice channel.
|
is not currently in a voice channel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('session_id', 'deaf', 'mute', 'self_mute',
|
__slots__ = (
|
||||||
'self_stream', 'self_video', 'self_deaf', 'afk', 'channel',
|
'session_id',
|
||||||
'requested_to_speak_at', 'suppress')
|
'deaf',
|
||||||
|
'mute',
|
||||||
|
'self_mute',
|
||||||
|
'self_stream',
|
||||||
|
'self_video',
|
||||||
|
'self_deaf',
|
||||||
|
'afk',
|
||||||
|
'channel',
|
||||||
|
'requested_to_speak_at',
|
||||||
|
'suppress',
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *, data, channel=None):
|
def __init__(self, *, data: VoiceStatePayload, channel: Optional[VocalGuildChannel] = None):
|
||||||
self.session_id = data.get('session_id')
|
self.session_id: str = data.get('session_id')
|
||||||
self._update(data, channel)
|
self._update(data, channel)
|
||||||
|
|
||||||
def _update(self, data, channel):
|
def _update(self, data: VoiceStatePayload, channel: Optional[VocalGuildChannel]):
|
||||||
self.self_mute = data.get('self_mute', False)
|
self.self_mute: bool = data.get('self_mute', False)
|
||||||
self.self_deaf = data.get('self_deaf', False)
|
self.self_deaf: bool = data.get('self_deaf', False)
|
||||||
self.self_stream = data.get('self_stream', False)
|
self.self_stream: bool = data.get('self_stream', False)
|
||||||
self.self_video = data.get('self_video', False)
|
self.self_video: bool = data.get('self_video', False)
|
||||||
self.afk = data.get('suppress', False)
|
self.afk: bool = data.get('suppress', False)
|
||||||
self.mute = data.get('mute', False)
|
self.mute: bool = data.get('mute', False)
|
||||||
self.deaf = data.get('deaf', False)
|
self.deaf: bool = data.get('deaf', False)
|
||||||
self.suppress = data.get('suppress', False)
|
self.suppress: bool = data.get('suppress', False)
|
||||||
self.requested_to_speak_at = utils.parse_time(data.get('request_to_speak_timestamp'))
|
self.requested_to_speak_at: Optional[datetime.datetime] = utils.parse_time(data.get('request_to_speak_timestamp'))
|
||||||
self.channel = channel
|
self.channel: Optional[VocalGuildChannel] = channel
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
attrs = [
|
attrs = [
|
||||||
('self_mute', self.self_mute),
|
('self_mute', self.self_mute),
|
||||||
('self_deaf', self.self_deaf),
|
('self_deaf', self.self_deaf),
|
||||||
('self_stream', self.self_stream),
|
('self_stream', self.self_stream),
|
||||||
('suppress', self.suppress),
|
('suppress', self.suppress),
|
||||||
('requested_to_speak_at', self.requested_to_speak_at),
|
('requested_to_speak_at', self.requested_to_speak_at),
|
||||||
('channel', self.channel)
|
('channel', self.channel),
|
||||||
]
|
]
|
||||||
inner = ' '.join('%s=%r' % t for t in attrs)
|
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||||
return f'<{self.__class__.__name__} {inner}>'
|
return f'<{self.__class__.__name__} {inner}>'
|
||||||
|
|
||||||
|
|
||||||
def flatten_user(cls):
|
def flatten_user(cls):
|
||||||
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
|
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
|
||||||
# ignore private/special methods
|
# ignore private/special methods
|
||||||
@@ -150,9 +178,12 @@ def flatten_user(cls):
|
|||||||
def generate_function(x):
|
def generate_function(x):
|
||||||
# We want sphinx to properly show coroutine functions as coroutines
|
# We want sphinx to properly show coroutine functions as coroutines
|
||||||
if inspect.iscoroutinefunction(value):
|
if inspect.iscoroutinefunction(value):
|
||||||
async def general(self, *args, **kwargs):
|
|
||||||
|
async def general(self, *args, **kwargs): # type: ignore
|
||||||
return await getattr(self._user, x)(*args, **kwargs)
|
return await getattr(self._user, x)(*args, **kwargs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
def general(self, *args, **kwargs):
|
def general(self, *args, **kwargs):
|
||||||
return getattr(self._user, x)(*args, **kwargs)
|
return getattr(self._user, x)(*args, **kwargs)
|
||||||
|
|
||||||
@@ -165,10 +196,12 @@ def flatten_user(cls):
|
|||||||
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
_BaseUser = discord.abc.User
|
|
||||||
|
M = TypeVar('M', bound='Member')
|
||||||
|
|
||||||
|
|
||||||
@flatten_user
|
@flatten_user
|
||||||
class Member(discord.abc.Messageable, _BaseUser):
|
class Member(discord.abc.Messageable, _UserTag):
|
||||||
"""Represents a Discord member to a :class:`Guild`.
|
"""Represents a Discord member to a :class:`Guild`.
|
||||||
|
|
||||||
This implements a lot of the functionality of :class:`User`.
|
This implements a lot of the functionality of :class:`User`.
|
||||||
@@ -193,6 +226,10 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
|
|
||||||
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`]
|
||||||
@@ -217,69 +254,101 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
premium_since: Optional[:class:`datetime.datetime`]
|
premium_since: Optional[:class:`datetime.datetime`]
|
||||||
An aware datetime object that specifies the date and time in UTC when the member used their
|
An aware datetime object that specifies the date and time in UTC when the member used their
|
||||||
Nitro boost on the guild, if available. This could be ``None``.
|
"Nitro boost" on the guild, if available. This could be ``None``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_roles', 'joined_at', 'premium_since', '_client_status',
|
__slots__ = (
|
||||||
'activities', 'guild', 'pending', 'nick', '_user', '_state')
|
'_roles',
|
||||||
|
'joined_at',
|
||||||
|
'premium_since',
|
||||||
|
'activities',
|
||||||
|
'guild',
|
||||||
|
'pending',
|
||||||
|
'nick',
|
||||||
|
'_client_status',
|
||||||
|
'_user',
|
||||||
|
'_state',
|
||||||
|
'_avatar',
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *, data, guild, state):
|
if TYPE_CHECKING:
|
||||||
self._state = state
|
name: str
|
||||||
self._user = state.store_user(data['user'])
|
id: int
|
||||||
self.guild = guild
|
discriminator: str
|
||||||
self.joined_at = utils.parse_time(data.get('joined_at'))
|
bot: bool
|
||||||
self.premium_since = utils.parse_time(data.get('premium_since'))
|
system: bool
|
||||||
self._update_roles(data)
|
created_at: datetime.datetime
|
||||||
self._client_status = {
|
default_avatar: Asset
|
||||||
None: 'offline'
|
avatar: Optional[Asset]
|
||||||
}
|
dm_channel: Optional[DMChannel]
|
||||||
self.activities = []
|
create_dm = User.create_dm
|
||||||
self.nick = data.get('nick', None)
|
mutual_guilds: List[Guild]
|
||||||
self.pending = data.get('pending', False)
|
public_flags: PublicUserFlags
|
||||||
|
banner: Optional[Asset]
|
||||||
|
accent_color: Optional[Colour]
|
||||||
|
accent_colour: Optional[Colour]
|
||||||
|
|
||||||
def __str__(self):
|
def __init__(self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState):
|
||||||
|
self._state: ConnectionState = state
|
||||||
|
self._user: User = state.store_user(data['user'])
|
||||||
|
self.guild: Guild = guild
|
||||||
|
self.joined_at: Optional[datetime.datetime] = utils.parse_time(data.get('joined_at'))
|
||||||
|
self.premium_since: Optional[datetime.datetime] = utils.parse_time(data.get('premium_since'))
|
||||||
|
self._roles: utils.SnowflakeList = utils.SnowflakeList(map(int, data['roles']))
|
||||||
|
self._client_status: Dict[Optional[str], str] = {None: 'offline'}
|
||||||
|
self.activities: Tuple[ActivityTypes, ...] = tuple()
|
||||||
|
self.nick: Optional[str] = data.get('nick', None)
|
||||||
|
self.pending: bool = data.get('pending', False)
|
||||||
|
self._avatar: Optional[str] = data.get('avatar')
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
return str(self._user)
|
return str(self._user)
|
||||||
|
|
||||||
def __repr__(self):
|
def __int__(self) -> int:
|
||||||
return f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}' \
|
return self.id
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}'
|
||||||
f' bot={self._user.bot} nick={self.nick!r} guild={self.guild!r}>'
|
f' bot={self._user.bot} nick={self.nick!r} guild={self.guild!r}>'
|
||||||
|
)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return isinstance(other, _BaseUser) and other.id == self.id
|
return isinstance(other, _UserTag) and other.id == self.id
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash(self._user)
|
return hash(self._user)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_message(cls, *, message, data):
|
def _from_message(cls: Type[M], *, message: Message, data: MemberPayload) -> M:
|
||||||
author = message.author
|
author = message.author
|
||||||
data['user'] = author._to_minimal_user_json()
|
data['user'] = author._to_minimal_user_json() # type: ignore
|
||||||
return cls(data=data, guild=message.guild, state=message._state)
|
return cls(data=data, guild=message.guild, state=message._state) # type: ignore
|
||||||
|
|
||||||
def _update_from_message(self, data):
|
def _update_from_message(self, data: MemberPayload) -> None:
|
||||||
self.joined_at = utils.parse_time(data.get('joined_at'))
|
self.joined_at = utils.parse_time(data.get('joined_at'))
|
||||||
self.premium_since = utils.parse_time(data.get('premium_since'))
|
self.premium_since = utils.parse_time(data.get('premium_since'))
|
||||||
self._update_roles(data)
|
self._roles = utils.SnowflakeList(map(int, data['roles']))
|
||||||
self.nick = data.get('nick', None)
|
self.nick = data.get('nick', None)
|
||||||
self.pending = data.get('pending', False)
|
self.pending = data.get('pending', False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _try_upgrade(cls, *, data, guild, state):
|
def _try_upgrade(cls: Type[M], *, data: UserWithMemberPayload, guild: Guild, state: ConnectionState) -> Union[User, M]:
|
||||||
# A User object with a 'member' key
|
# A User object with a 'member' key
|
||||||
try:
|
try:
|
||||||
member_data = data.pop('member')
|
member_data = data.pop('member')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return state.store_user(data)
|
return state.create_user(data)
|
||||||
else:
|
else:
|
||||||
member_data['user'] = data
|
member_data['user'] = data # type: ignore
|
||||||
return cls(data=member_data, guild=guild, state=state)
|
return cls(data=member_data, guild=guild, state=state) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _copy(cls, member):
|
def _copy(cls: Type[M], member: M) -> M:
|
||||||
self = cls.__new__(cls) # to bypass __init__
|
self: M = cls.__new__(cls) # to bypass __init__
|
||||||
|
|
||||||
self._roles = utils.SnowflakeList(member._roles, is_sorted=True)
|
self._roles = utils.SnowflakeList(member._roles, is_sorted=True)
|
||||||
self.joined_at = member.joined_at
|
self.joined_at = member.joined_at
|
||||||
@@ -290,6 +359,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
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
|
||||||
@@ -300,10 +370,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
ch = await self.create_dm()
|
ch = await self.create_dm()
|
||||||
return ch
|
return ch
|
||||||
|
|
||||||
def _update_roles(self, data):
|
def _update(self, data: MemberPayload) -> None:
|
||||||
self._roles = utils.SnowflakeList(map(int, data['roles']))
|
|
||||||
|
|
||||||
def _update(self, data):
|
|
||||||
# the nickname change is optional,
|
# the nickname change is optional,
|
||||||
# if it isn't in the payload then it didn't change
|
# if it isn't in the payload then it didn't change
|
||||||
try:
|
try:
|
||||||
@@ -317,21 +384,21 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
self.premium_since = utils.parse_time(data.get('premium_since'))
|
self.premium_since = utils.parse_time(data.get('premium_since'))
|
||||||
self._update_roles(data)
|
self._roles = utils.SnowflakeList(map(int, data['roles']))
|
||||||
|
self._avatar = data.get('avatar')
|
||||||
|
|
||||||
def _presence_update(self, data, 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']))
|
||||||
self._client_status = {
|
self._client_status = {
|
||||||
sys.intern(key): sys.intern(value)
|
sys.intern(key): sys.intern(value) for key, value in data.get('client_status', {}).items() # type: ignore
|
||||||
for key, value in data.get('client_status', {}).items()
|
|
||||||
}
|
}
|
||||||
self._client_status[None] = sys.intern(data['status'])
|
self._client_status[None] = sys.intern(data['status'])
|
||||||
|
|
||||||
if len(user) > 1:
|
if len(user) > 1:
|
||||||
return self._update_inner_user(user)
|
return self._update_inner_user(user)
|
||||||
return False
|
return None
|
||||||
|
|
||||||
def _update_inner_user(self, user):
|
def _update_inner_user(self, user: UserPayload) -> Optional[Tuple[User, User]]:
|
||||||
u = self._user
|
u = self._user
|
||||||
original = (u.name, u._avatar, u.discriminator, u._public_flags)
|
original = (u.name, u._avatar, u.discriminator, u._public_flags)
|
||||||
# These keys seem to always be available
|
# These keys seem to always be available
|
||||||
@@ -343,12 +410,12 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return to_return, u
|
return to_return, u
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self):
|
def status(self) -> Status:
|
||||||
""":class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead."""
|
""":class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead."""
|
||||||
return try_enum(Status, self._client_status[None])
|
return try_enum(Status, self._client_status[None])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def raw_status(self):
|
def raw_status(self) -> str:
|
||||||
""":class:`str`: The member's overall status as a string value.
|
""":class:`str`: The member's overall status as a string value.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
@@ -356,31 +423,31 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return self._client_status[None]
|
return self._client_status[None]
|
||||||
|
|
||||||
@status.setter
|
@status.setter
|
||||||
def status(self, value):
|
def status(self, value: Status) -> None:
|
||||||
# internal use only
|
# internal use only
|
||||||
self._client_status[None] = str(value)
|
self._client_status[None] = str(value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mobile_status(self):
|
def mobile_status(self) -> Status:
|
||||||
""":class:`Status`: The member's status on a mobile device, if applicable."""
|
""":class:`Status`: The member's status on a mobile device, if applicable."""
|
||||||
return try_enum(Status, self._client_status.get('mobile', 'offline'))
|
return try_enum(Status, self._client_status.get('mobile', 'offline'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def desktop_status(self):
|
def desktop_status(self) -> Status:
|
||||||
""":class:`Status`: The member's status on the desktop client, if applicable."""
|
""":class:`Status`: The member's status on the desktop client, if applicable."""
|
||||||
return try_enum(Status, self._client_status.get('desktop', 'offline'))
|
return try_enum(Status, self._client_status.get('desktop', 'offline'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def web_status(self):
|
def web_status(self) -> Status:
|
||||||
""":class:`Status`: The member's status on the web client, if applicable."""
|
""":class:`Status`: The member's status on the web client, if applicable."""
|
||||||
return try_enum(Status, self._client_status.get('web', 'offline'))
|
return try_enum(Status, self._client_status.get('web', 'offline'))
|
||||||
|
|
||||||
def is_on_mobile(self):
|
def is_on_mobile(self) -> bool:
|
||||||
""":class:`bool`: A helper function that determines if a member is active on a mobile device."""
|
""":class:`bool`: A helper function that determines if a member is active on a mobile device."""
|
||||||
return 'mobile' in self._client_status
|
return 'mobile' in self._client_status
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def colour(self):
|
def colour(self) -> Colour:
|
||||||
""":class:`Colour`: A property that returns a colour denoting the rendered colour
|
""":class:`Colour`: A property that returns a colour denoting the rendered colour
|
||||||
for the member. If the default colour is the one rendered then an instance
|
for the member. If the default colour is the one rendered then an instance
|
||||||
of :meth:`Colour.default` is returned.
|
of :meth:`Colour.default` is returned.
|
||||||
@@ -399,7 +466,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return Colour.default()
|
return Colour.default()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color(self):
|
def color(self) -> Colour:
|
||||||
""":class:`Colour`: A property that returns a color denoting the rendered color for
|
""":class:`Colour`: A property that returns a color denoting the rendered color for
|
||||||
the member. If the default color is the one rendered then an instance of :meth:`Colour.default`
|
the member. If the default color is the one rendered then an instance of :meth:`Colour.default`
|
||||||
is returned.
|
is returned.
|
||||||
@@ -409,7 +476,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return self.colour
|
return self.colour
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def roles(self):
|
def roles(self) -> List[Role]:
|
||||||
"""List[:class:`Role`]: A :class:`list` of :class:`Role` that the member belongs to. Note
|
"""List[:class:`Role`]: A :class:`list` of :class:`Role` that the member belongs to. Note
|
||||||
that the first element of this list is always the default '@everyone'
|
that the first element of this list is always the default '@everyone'
|
||||||
role.
|
role.
|
||||||
@@ -427,14 +494,14 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mention(self):
|
def mention(self) -> str:
|
||||||
""":class:`str`: Returns a string that allows you to mention the member."""
|
""":class:`str`: Returns a string that allows you to mention the member."""
|
||||||
if self.nick:
|
if self.nick:
|
||||||
return f'<@!{self._user.id}>'
|
return f'<@!{self._user.id}>'
|
||||||
return f'<@{self._user.id}>'
|
return f'<@{self._user.id}>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self) -> str:
|
||||||
""":class:`str`: Returns the user's display name.
|
""":class:`str`: Returns the user's display name.
|
||||||
|
|
||||||
For regular users this is just their username, but
|
For regular users this is just their username, but
|
||||||
@@ -444,8 +511,31 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return self.nick or self.name
|
return self.nick or self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self):
|
def display_avatar(self) -> Asset:
|
||||||
"""Union[:class:`BaseActivity`, :class:`Spotify`]: Returns the primary
|
""":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
|
||||||
|
def activity(self) -> Optional[ActivityTypes]:
|
||||||
|
"""Optional[Union[:class:`BaseActivity`, :class:`Spotify`]]: Returns the primary
|
||||||
activity the user is currently doing. Could be ``None`` if no activity is being done.
|
activity the user is currently doing. Could be ``None`` if no activity is being done.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -461,7 +551,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
if self.activities:
|
if self.activities:
|
||||||
return self.activities[0]
|
return self.activities[0]
|
||||||
|
|
||||||
def mentioned_in(self, message):
|
def mentioned_in(self, message: Message) -> bool:
|
||||||
"""Checks if the member is mentioned in the specified message.
|
"""Checks if the member is mentioned in the specified message.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -483,7 +573,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return any(self._roles.has(role.id) for role in message.role_mentions)
|
return any(self._roles.has(role.id) for role in message.role_mentions)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def top_role(self):
|
def top_role(self) -> Role:
|
||||||
""":class:`Role`: Returns the member's highest role.
|
""":class:`Role`: Returns the member's highest role.
|
||||||
|
|
||||||
This is useful for figuring where a member stands in the role
|
This is useful for figuring where a member stands in the role
|
||||||
@@ -496,7 +586,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return max(guild.get_role(rid) or guild.default_role for rid in self._roles)
|
return max(guild.get_role(rid) or guild.default_role for rid in self._roles)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guild_permissions(self):
|
def guild_permissions(self) -> Permissions:
|
||||||
""":class:`Permissions`: Returns the member's guild permissions.
|
""":class:`Permissions`: Returns the member's guild permissions.
|
||||||
|
|
||||||
This only takes into consideration the guild permissions
|
This only takes into consideration the guild permissions
|
||||||
@@ -521,29 +611,21 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def voice(self):
|
def voice(self) -> Optional[VoiceState]:
|
||||||
"""Optional[:class:`VoiceState`]: Returns the member's current voice state."""
|
"""Optional[:class:`VoiceState`]: Returns the member's current voice state."""
|
||||||
return self.guild._voice_state_for(self._user.id)
|
return self.guild._voice_state_for(self._user.id)
|
||||||
|
|
||||||
@overload
|
|
||||||
async def ban(
|
async def ban(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
reason: Optional[str] = ...,
|
delete_message_days: Literal[0, 1, 2, 3, 4, 5, 6, 7] = 1,
|
||||||
delete_message_days: Literal[1, 2, 3, 4, 5, 6, 7] = ...,
|
reason: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
async def ban(self) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
async def ban(self, **kwargs):
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Bans this member. Equivalent to :meth:`Guild.ban`.
|
Bans this member. Equivalent to :meth:`Guild.ban`.
|
||||||
"""
|
"""
|
||||||
await self.guild.ban(self, **kwargs)
|
await self.guild.ban(self, reason=reason, delete_message_days=delete_message_days)
|
||||||
|
|
||||||
async def unban(self, *, reason: Optional[str] = None) -> None:
|
async def unban(self, *, reason: Optional[str] = None) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
@@ -559,25 +641,17 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
"""
|
"""
|
||||||
await self.guild.kick(self, reason=reason)
|
await self.guild.kick(self, reason=reason)
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(
|
async def edit(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
reason: Optional[str] = ...,
|
nick: Optional[str] = MISSING,
|
||||||
nick: Optional[str] = None,
|
mute: bool = MISSING,
|
||||||
mute: bool = ...,
|
deafen: bool = MISSING,
|
||||||
deafen: bool = ...,
|
suppress: bool = MISSING,
|
||||||
suppress: bool = ...,
|
roles: List[discord.abc.Snowflake] = MISSING,
|
||||||
roles: Optional[List[discord.abc.Snowflake]] = ...,
|
voice_channel: Optional[VocalGuildChannel] = MISSING,
|
||||||
voice_channel: Optional[VocalGuildChannel] = ...,
|
reason: Optional[str] = None,
|
||||||
) -> None:
|
) -> Optional[Member]:
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(self) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
async def edit(self, *, reason=None, **fields):
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the member's data.
|
Edits the member's data.
|
||||||
@@ -603,6 +677,9 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
.. 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`]
|
||||||
@@ -616,7 +693,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
roles: Optional[List[:class:`Role`]]
|
roles: List[:class:`Role`]
|
||||||
The member's new list of roles. This *replaces* the roles.
|
The member's new list of roles. This *replaces* the roles.
|
||||||
voice_channel: Optional[:class:`VoiceChannel`]
|
voice_channel: Optional[:class:`VoiceChannel`]
|
||||||
The voice channel to move the member to.
|
The voice channel to move the member to.
|
||||||
@@ -630,34 +707,32 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
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
|
||||||
me = self._state.self_id == self.id
|
me = self._state.self_id == self.id
|
||||||
payload = {}
|
payload: Dict[str, Any] = {}
|
||||||
|
|
||||||
try:
|
if nick is not MISSING:
|
||||||
nick = fields['nick']
|
|
||||||
except KeyError:
|
|
||||||
# nick not present so...
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
nick = nick or ''
|
nick = nick or ''
|
||||||
if me:
|
if me:
|
||||||
await http.change_my_nickname(guild_id, nick, reason=reason)
|
await http.change_my_nickname(guild_id, nick, reason=reason)
|
||||||
else:
|
else:
|
||||||
payload['nick'] = nick
|
payload['nick'] = nick
|
||||||
|
|
||||||
deafen = fields.get('deafen')
|
if deafen is not MISSING:
|
||||||
if deafen is not None:
|
|
||||||
payload['deaf'] = deafen
|
payload['deaf'] = deafen
|
||||||
|
|
||||||
mute = fields.get('mute')
|
if mute is not MISSING:
|
||||||
if mute is not None:
|
|
||||||
payload['mute'] = mute
|
payload['mute'] = mute
|
||||||
|
|
||||||
suppress = fields.get('suppress')
|
if suppress is not MISSING:
|
||||||
if suppress is not None:
|
|
||||||
voice_state_payload = {
|
voice_state_payload = {
|
||||||
'channel_id': self.voice.channel.id,
|
'channel_id': self.voice.channel.id,
|
||||||
'suppress': suppress,
|
'suppress': suppress,
|
||||||
@@ -673,26 +748,17 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
voice_state_payload['request_to_speak_timestamp'] = datetime.datetime.utcnow().isoformat()
|
voice_state_payload['request_to_speak_timestamp'] = datetime.datetime.utcnow().isoformat()
|
||||||
await http.edit_voice_state(guild_id, self.id, voice_state_payload)
|
await http.edit_voice_state(guild_id, self.id, voice_state_payload)
|
||||||
|
|
||||||
try:
|
if voice_channel is not MISSING:
|
||||||
vc = fields['voice_channel']
|
payload['channel_id'] = voice_channel and voice_channel.id
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
payload['channel_id'] = vc and vc.id
|
|
||||||
|
|
||||||
try:
|
if roles is not MISSING:
|
||||||
roles = fields['roles']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
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)
|
||||||
|
|
||||||
# TODO: wait for WS event for modify-in-place behaviour
|
async def request_to_speak(self) -> None:
|
||||||
|
|
||||||
async def request_to_speak(self):
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Request to speak in the connected channel.
|
Request to speak in the connected channel.
|
||||||
@@ -747,7 +813,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
"""
|
"""
|
||||||
await self.edit(voice_channel=channel, reason=reason)
|
await self.edit(voice_channel=channel, reason=reason)
|
||||||
|
|
||||||
async def add_roles(self, *roles: Snowflake, reason: Optional[str] = None, atomic: bool = True):
|
async def add_roles(self, *roles: Snowflake, reason: Optional[str] = None, atomic: bool = True) -> None:
|
||||||
r"""|coro|
|
r"""|coro|
|
||||||
|
|
||||||
Gives the member a number of :class:`Role`\s.
|
Gives the member a number of :class:`Role`\s.
|
||||||
@@ -831,7 +897,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
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[discord.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
|
||||||
|
|||||||
@@ -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 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
|
||||||
@@ -42,10 +42,10 @@ from .embeds import Embed
|
|||||||
from .member import Member
|
from .member import Member
|
||||||
from .flags import MessageFlags
|
from .flags import MessageFlags
|
||||||
from .file import File
|
from .file import File
|
||||||
from .utils import escape_mentions
|
from .utils import escape_mentions, MISSING
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
from .sticker import Sticker
|
from .sticker import StickerItem
|
||||||
from .threads import Thread
|
from .threads import Thread
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -60,16 +60,23 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from .types.components import Component as ComponentPayload
|
from .types.components import Component as ComponentPayload
|
||||||
from .types.threads import ThreadArchiveDuration
|
from .types.threads import ThreadArchiveDuration
|
||||||
from .types.member import Member as MemberPayload
|
from .types.member import (
|
||||||
|
Member as MemberPayload,
|
||||||
|
UserWithMember as UserWithMemberPayload,
|
||||||
|
)
|
||||||
from .types.user import User as UserPayload
|
from .types.user import User as UserPayload
|
||||||
from .types.embed import Embed as EmbedPayload
|
from .types.embed import Embed as EmbedPayload
|
||||||
from .abc import Snowflake
|
from .abc import Snowflake
|
||||||
from .abc import GuildChannel
|
from .abc import GuildChannel, PartialMessageableChannel, MessageableChannel
|
||||||
|
from .components import Component
|
||||||
from .state import ConnectionState
|
from .state import ConnectionState
|
||||||
from .channel import TextChannel, GroupChannel, DMChannel
|
from .channel import TextChannel, GroupChannel, DMChannel, PartialMessageable
|
||||||
from .mentions import AllowedMentions
|
from .mentions import AllowedMentions
|
||||||
|
from .user import User
|
||||||
|
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__ = (
|
||||||
@@ -118,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.
|
||||||
|
|
||||||
@@ -149,15 +160,15 @@ class Attachment(Hashable):
|
|||||||
__slots__ = ('id', 'size', 'height', 'width', 'filename', 'url', 'proxy_url', '_http', 'content_type')
|
__slots__ = ('id', 'size', 'height', 'width', 'filename', 'url', 'proxy_url', '_http', 'content_type')
|
||||||
|
|
||||||
def __init__(self, *, data: AttachmentPayload, state: ConnectionState):
|
def __init__(self, *, data: AttachmentPayload, state: ConnectionState):
|
||||||
self.id = int(data['id'])
|
self.id: int = int(data['id'])
|
||||||
self.size = data['size']
|
self.size: int = data['size']
|
||||||
self.height = data.get('height')
|
self.height: Optional[int] = data.get('height')
|
||||||
self.width = data.get('width')
|
self.width: Optional[int] = data.get('width')
|
||||||
self.filename = data['filename']
|
self.filename: str = data['filename']
|
||||||
self.url = data.get('url')
|
self.url: str = data.get('url')
|
||||||
self.proxy_url = data.get('proxy_url')
|
self.proxy_url: str = data.get('proxy_url')
|
||||||
self._http = state.http
|
self._http = state.http
|
||||||
self.content_type = data.get('content_type')
|
self.content_type: Optional[str] = data.get('content_type')
|
||||||
|
|
||||||
def is_spoiler(self) -> bool:
|
def is_spoiler(self) -> bool:
|
||||||
""":class:`bool`: Whether this attachment contains a spoiler."""
|
""":class:`bool`: Whether this attachment contains a spoiler."""
|
||||||
@@ -327,7 +338,7 @@ class DeletedReferencedMessage:
|
|||||||
__slots__ = ('_parent',)
|
__slots__ = ('_parent',)
|
||||||
|
|
||||||
def __init__(self, parent: MessageReference):
|
def __init__(self, parent: MessageReference):
|
||||||
self._parent = parent
|
self._parent: MessageReference = parent
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<DeletedReferencedMessage id={self.id} channel_id={self.channel_id} guild_id={self.guild_id!r}>"
|
return f"<DeletedReferencedMessage id={self.id} channel_id={self.channel_id} guild_id={self.guild_id!r}>"
|
||||||
@@ -335,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:
|
||||||
@@ -387,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 = message_id
|
self.message_id: Optional[int] = message_id
|
||||||
self.channel_id = channel_id
|
self.channel_id: int = channel_id
|
||||||
self.guild_id = guild_id
|
self.guild_id: Optional[int] = guild_id
|
||||||
self.fail_if_not_exists = 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'))
|
||||||
@@ -404,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
|
||||||
@@ -451,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
|
||||||
|
|
||||||
@@ -495,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`
|
||||||
@@ -509,12 +529,12 @@ class Message(Hashable):
|
|||||||
private channel or the user has the left the guild, then it is a :class:`User` instead.
|
private channel or the user has the left the guild, then it is a :class:`User` instead.
|
||||||
content: :class:`str`
|
content: :class:`str`
|
||||||
The actual contents of the message.
|
The actual contents of the message.
|
||||||
nonce: Union[:class:`str`, :class:`int`]
|
nonce: Optional[Union[:class:`str`, :class:`int`]]
|
||||||
The value used by the discord guild and the client to verify that the message is successfully sent.
|
The value used by the discord guild and the client to verify that the message is successfully sent.
|
||||||
This is not stored long term within Discord's servers and is only used ephemerally.
|
This is not stored long term within Discord's servers and is only used ephemerally.
|
||||||
embeds: List[:class:`Embed`]
|
embeds: List[:class:`Embed`]
|
||||||
A list of embeds the message has.
|
A list of embeds the message has.
|
||||||
channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`]
|
channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`, :class:`GroupChannel`, :class:`PartialMessageable`]
|
||||||
The :class:`TextChannel` or :class:`Thread` that the message was sent from.
|
The :class:`TextChannel` or :class:`Thread` that the message was sent from.
|
||||||
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
|
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
|
||||||
reference: Optional[:class:`~discord.MessageReference`]
|
reference: Optional[:class:`~discord.MessageReference`]
|
||||||
@@ -582,14 +602,16 @@ class Message(Hashable):
|
|||||||
- ``description``: A string representing the application's description.
|
- ``description``: A string representing the application's description.
|
||||||
- ``icon``: A string representing the icon ID of the application.
|
- ``icon``: A string representing the icon ID of the application.
|
||||||
- ``cover_image``: A string representing the embed's image asset ID.
|
- ``cover_image``: A string representing the embed's image asset ID.
|
||||||
stickers: List[:class:`Sticker`]
|
stickers: List[:class:`StickerItem`]
|
||||||
A list of stickers given to the message.
|
A list of sticker items given to the message.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
components: List[:class:`Component`]
|
components: List[:class:`Component`]
|
||||||
A list of components in the message.
|
A list of components in the message.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
guild: Optional[:class:`Guild`]
|
||||||
|
The guild that the message belongs to, if applicable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
@@ -601,7 +623,6 @@ class Message(Hashable):
|
|||||||
'_cs_raw_channel_mentions',
|
'_cs_raw_channel_mentions',
|
||||||
'_cs_raw_role_mentions',
|
'_cs_raw_role_mentions',
|
||||||
'_cs_system_content',
|
'_cs_system_content',
|
||||||
'_cs_guild',
|
|
||||||
'tts',
|
'tts',
|
||||||
'content',
|
'content',
|
||||||
'channel',
|
'channel',
|
||||||
@@ -623,38 +644,50 @@ class Message(Hashable):
|
|||||||
'activity',
|
'activity',
|
||||||
'stickers',
|
'stickers',
|
||||||
'components',
|
'components',
|
||||||
|
'guild',
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
_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]
|
||||||
|
reference: Optional[MessageReference]
|
||||||
|
mentions: List[Union[User, Member]]
|
||||||
|
author: Union[User, Member]
|
||||||
|
role_mentions: List[Role]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
state: ConnectionState,
|
state: ConnectionState,
|
||||||
channel: Union[TextChannel, Thread, DMChannel, GroupChannel],
|
channel: MessageableChannel,
|
||||||
data: MessagePayload,
|
data: MessagePayload,
|
||||||
):
|
):
|
||||||
self._state = state
|
self._state: ConnectionState = state
|
||||||
self.id = int(data['id'])
|
self.id: int = int(data['id'])
|
||||||
self.webhook_id = utils._get_as_snowflake(data, 'webhook_id')
|
self.webhook_id: Optional[int] = utils._get_as_snowflake(data, 'webhook_id')
|
||||||
self.reactions = [Reaction(message=self, data=d) for d in data.get('reactions', [])]
|
self.reactions: List[Reaction] = [Reaction(message=self, data=d) for d in data.get('reactions', [])]
|
||||||
self.attachments = [Attachment(data=a, state=self._state) for a in data['attachments']]
|
self.attachments: List[Attachment] = [Attachment(data=a, state=self._state) for a in data['attachments']]
|
||||||
self.embeds = [Embed.from_dict(a) for a in data['embeds']]
|
self.embeds: List[Embed] = [Embed.from_dict(a) for a in data['embeds']]
|
||||||
self.application = data.get('application')
|
self.application: Optional[MessageApplicationPayload] = data.get('application')
|
||||||
self.activity = data.get('activity')
|
self.activity: Optional[MessageActivityPayload] = data.get('activity')
|
||||||
self.channel = channel
|
self.channel: MessageableChannel = channel
|
||||||
self._edited_timestamp = utils.parse_time(data['edited_timestamp'])
|
self._edited_timestamp: Optional[datetime.datetime] = utils.parse_time(data['edited_timestamp'])
|
||||||
self.type = try_enum(MessageType, data['type'])
|
self.type: MessageType = try_enum(MessageType, data['type'])
|
||||||
self.pinned = data['pinned']
|
self.pinned: bool = data['pinned']
|
||||||
self.flags = MessageFlags._from_value(data.get('flags', 0))
|
self.flags: MessageFlags = MessageFlags._from_value(data.get('flags', 0))
|
||||||
self.mention_everyone = data['mention_everyone']
|
self.mention_everyone: bool = data['mention_everyone']
|
||||||
self.tts = data['tts']
|
self.tts: bool = data['tts']
|
||||||
self.content = data['content']
|
self.content: str = data['content']
|
||||||
self.nonce = data.get('nonce')
|
self.nonce: Optional[Union[int, str]] = data.get('nonce')
|
||||||
self.stickers = [Sticker(data=d, state=state) for d in data.get('stickers', [])]
|
self.stickers: List[StickerItem] = [StickerItem(data=d, state=state) for d in data.get('sticker_items', [])]
|
||||||
self.components = [_component_factory(d) for d in data.get('components', [])]
|
self.components: List[Component] = [_component_factory(d) for d in data.get('components', [])]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# if the channel doesn't have a guild attribute, we handle that
|
||||||
|
self.guild = channel.guild # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
self.guild = state._get_guild(utils._get_as_snowflake(data, 'guild_id'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ref = data['message_reference']
|
ref = data['message_reference']
|
||||||
@@ -676,19 +709,25 @@ 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', 'flags'):
|
for handler in ('author', 'member', 'mentions', 'mention_roles'):
|
||||||
try:
|
try:
|
||||||
getattr(self, f'_handle_{handler}')(data[handler])
|
getattr(self, f'_handle_{handler}')(data[handler])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
name = self.__class__.__name__
|
||||||
return (
|
return (
|
||||||
f'<Message 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]
|
||||||
@@ -768,7 +807,7 @@ class Message(Hashable):
|
|||||||
def _handle_edited_timestamp(self, value: str) -> None:
|
def _handle_edited_timestamp(self, value: str) -> None:
|
||||||
self._edited_timestamp = utils.parse_time(value)
|
self._edited_timestamp = utils.parse_time(value)
|
||||||
|
|
||||||
def _handle_pinned(self, value: int) -> None:
|
def _handle_pinned(self, value: bool) -> None:
|
||||||
self.pinned = value
|
self.pinned = value
|
||||||
|
|
||||||
def _handle_flags(self, value: int) -> None:
|
def _handle_flags(self, value: int) -> None:
|
||||||
@@ -824,7 +863,7 @@ class Message(Hashable):
|
|||||||
# TODO: consider adding to cache here
|
# TODO: consider adding to cache here
|
||||||
self.author = Member._from_message(message=self, data=member)
|
self.author = Member._from_message(message=self, data=member)
|
||||||
|
|
||||||
def _handle_mentions(self, mentions: List[UserPayload]) -> None:
|
def _handle_mentions(self, mentions: List[UserWithMemberPayload]) -> None:
|
||||||
self.mentions = r = []
|
self.mentions = r = []
|
||||||
guild = self.guild
|
guild = self.guild
|
||||||
state = self._state
|
state = self._state
|
||||||
@@ -851,19 +890,10 @@ class Message(Hashable):
|
|||||||
def _handle_components(self, components: List[ComponentPayload]):
|
def _handle_components(self, components: List[ComponentPayload]):
|
||||||
self.components = [_component_factory(d) for d in components]
|
self.components = [_component_factory(d) for d in components]
|
||||||
|
|
||||||
def _rebind_channel_reference(self, new_channel: Union[TextChannel, Thread, DMChannel, GroupChannel]) -> None:
|
def _rebind_cached_references(self, new_guild: Guild, new_channel: Union[TextChannel, Thread]) -> None:
|
||||||
|
self.guild = new_guild
|
||||||
self.channel = new_channel
|
self.channel = new_channel
|
||||||
|
|
||||||
try:
|
|
||||||
del self._cs_guild # type: ignore
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_guild')
|
|
||||||
def guild(self) -> Optional[Guild]:
|
|
||||||
"""Optional[:class:`Guild`]: The guild that the message belongs to, if applicable."""
|
|
||||||
return getattr(self.channel, 'guild', None)
|
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_raw_mentions')
|
@utils.cached_slot_property('_cs_raw_mentions')
|
||||||
def raw_mentions(self) -> List[int]:
|
def raw_mentions(self) -> List[int]:
|
||||||
"""List[:class:`int`]: A property that returns an array of user IDs matched with
|
"""List[:class:`int`]: A property that returns an array of user IDs matched with
|
||||||
@@ -967,9 +997,17 @@ class Message(Hashable):
|
|||||||
def is_system(self) -> bool:
|
def is_system(self) -> bool:
|
||||||
""":class:`bool`: Whether the message is a system message.
|
""":class:`bool`: Whether the message is a system message.
|
||||||
|
|
||||||
|
A system message is a message that is constructed entirely by the Discord API
|
||||||
|
in response to something.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
"""
|
"""
|
||||||
return self.type is not MessageType.default
|
return self.type not in (
|
||||||
|
MessageType.default,
|
||||||
|
MessageType.reply,
|
||||||
|
MessageType.application_command,
|
||||||
|
MessageType.thread_starter_message,
|
||||||
|
)
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_system_content')
|
@utils.cached_slot_property('_cs_system_content')
|
||||||
def system_content(self):
|
def system_content(self):
|
||||||
@@ -984,21 +1022,27 @@ class Message(Hashable):
|
|||||||
if self.type is MessageType.default:
|
if self.type is MessageType.default:
|
||||||
return self.content
|
return self.content
|
||||||
|
|
||||||
if self.type is MessageType.pins_add:
|
|
||||||
return f'{self.author.name} pinned a message to this channel.'
|
|
||||||
|
|
||||||
if self.type is MessageType.recipient_add:
|
if self.type is MessageType.recipient_add:
|
||||||
|
if self.channel.type is ChannelType.group:
|
||||||
return f'{self.author.name} added {self.mentions[0].name} to the group.'
|
return f'{self.author.name} added {self.mentions[0].name} to the group.'
|
||||||
|
else:
|
||||||
|
return f'{self.author.name} added {self.mentions[0].name} to the thread.'
|
||||||
|
|
||||||
if self.type is MessageType.recipient_remove:
|
if self.type is MessageType.recipient_remove:
|
||||||
|
if self.channel.type is ChannelType.group:
|
||||||
return f'{self.author.name} removed {self.mentions[0].name} from the group.'
|
return f'{self.author.name} removed {self.mentions[0].name} from the group.'
|
||||||
|
else:
|
||||||
|
return f'{self.author.name} removed {self.mentions[0].name} from the thread.'
|
||||||
|
|
||||||
if self.type is MessageType.channel_name_change:
|
if self.type is MessageType.channel_name_change:
|
||||||
return f'{self.author.name} changed the channel name: {self.content}'
|
return f'{self.author.name} changed the channel name: **{self.content}**'
|
||||||
|
|
||||||
if self.type is MessageType.channel_icon_change:
|
if self.type is MessageType.channel_icon_change:
|
||||||
return f'{self.author.name} changed the channel icon.'
|
return f'{self.author.name} changed the channel icon.'
|
||||||
|
|
||||||
|
if self.type is MessageType.pins_add:
|
||||||
|
return f'{self.author.name} pinned a message to this channel.'
|
||||||
|
|
||||||
if self.type is MessageType.new_member:
|
if self.type is MessageType.new_member:
|
||||||
formats = [
|
formats = [
|
||||||
"{0} joined the party.",
|
"{0} joined the party.",
|
||||||
@@ -1020,21 +1064,34 @@ class Message(Hashable):
|
|||||||
return formats[created_at_ms % len(formats)].format(self.author.name)
|
return formats[created_at_ms % len(formats)].format(self.author.name)
|
||||||
|
|
||||||
if self.type is MessageType.premium_guild_subscription:
|
if self.type is MessageType.premium_guild_subscription:
|
||||||
|
if not self.content:
|
||||||
return f'{self.author.name} just boosted the server!'
|
return f'{self.author.name} just boosted the server!'
|
||||||
|
else:
|
||||||
|
return f'{self.author.name} just boosted the server **{self.content}** times!'
|
||||||
|
|
||||||
if self.type is MessageType.premium_guild_tier_1:
|
if self.type is MessageType.premium_guild_tier_1:
|
||||||
|
if not self.content:
|
||||||
return f'{self.author.name} just boosted the server! {self.guild} has achieved **Level 1!**'
|
return f'{self.author.name} just boosted the server! {self.guild} has achieved **Level 1!**'
|
||||||
|
else:
|
||||||
|
return f'{self.author.name} just boosted the server **{self.content}** times! {self.guild} has achieved **Level 1!**'
|
||||||
|
|
||||||
if self.type is MessageType.premium_guild_tier_2:
|
if self.type is MessageType.premium_guild_tier_2:
|
||||||
|
if not self.content:
|
||||||
return f'{self.author.name} just boosted the server! {self.guild} has achieved **Level 2!**'
|
return f'{self.author.name} just boosted the server! {self.guild} has achieved **Level 2!**'
|
||||||
|
else:
|
||||||
|
return f'{self.author.name} just boosted the server **{self.content}** times! {self.guild} has achieved **Level 2!**'
|
||||||
|
|
||||||
if self.type is MessageType.premium_guild_tier_3:
|
if self.type is MessageType.premium_guild_tier_3:
|
||||||
|
if not self.content:
|
||||||
return f'{self.author.name} just boosted the server! {self.guild} has achieved **Level 3!**'
|
return f'{self.author.name} just boosted the server! {self.guild} has achieved **Level 3!**'
|
||||||
|
else:
|
||||||
|
return f'{self.author.name} just boosted the server **{self.content}** times! {self.guild} has achieved **Level 3!**'
|
||||||
|
|
||||||
if self.type is MessageType.channel_follow_add:
|
if self.type is MessageType.channel_follow_add:
|
||||||
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:
|
||||||
@@ -1049,13 +1106,23 @@ class Message(Hashable):
|
|||||||
if self.type is MessageType.guild_discovery_grace_period_final_warning:
|
if self.type is MessageType.guild_discovery_grace_period_final_warning:
|
||||||
return 'This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.'
|
return 'This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.'
|
||||||
|
|
||||||
|
if self.type is MessageType.thread_created:
|
||||||
|
return f'{self.author.name} started a thread: **{self.content}**. See all **threads**.'
|
||||||
|
|
||||||
if self.type is MessageType.reply:
|
if self.type is MessageType.reply:
|
||||||
return self.content
|
return self.content
|
||||||
|
|
||||||
|
if self.type is MessageType.thread_starter_message:
|
||||||
|
if self.reference is None or self.reference.resolved is None:
|
||||||
|
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
|
||||||
|
|
||||||
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.
|
||||||
@@ -1066,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
|
||||||
------
|
------
|
||||||
@@ -1093,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(
|
||||||
@@ -1106,14 +1182,34 @@ 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
|
||||||
async def edit(self) -> None:
|
async def edit(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
content: Optional[str] = ...,
|
||||||
|
embeds: List[Embed] = ...,
|
||||||
|
attachments: List[Attachment] = ...,
|
||||||
|
suppress: bool = ...,
|
||||||
|
delete_after: Optional[float] = ...,
|
||||||
|
allowed_mentions: Optional[AllowedMentions] = ...,
|
||||||
|
view: Optional[View] = ...,
|
||||||
|
) -> Message:
|
||||||
...
|
...
|
||||||
|
|
||||||
async def edit(self, **fields) -> None:
|
async def edit(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = MISSING,
|
||||||
|
embed: Optional[Embed] = MISSING,
|
||||||
|
embeds: List[Embed] = MISSING,
|
||||||
|
attachments: List[Attachment] = MISSING,
|
||||||
|
suppress: bool = MISSING,
|
||||||
|
delete_after: Optional[float] = None,
|
||||||
|
allowed_mentions: Optional[AllowedMentions] = MISSING,
|
||||||
|
view: Optional[View] = MISSING,
|
||||||
|
) -> Message:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the message.
|
Edits the message.
|
||||||
@@ -1131,6 +1227,11 @@ class Message(Hashable):
|
|||||||
embed: Optional[:class:`Embed`]
|
embed: Optional[:class:`Embed`]
|
||||||
The new embed to replace the original with.
|
The new embed to replace the original with.
|
||||||
Could be ``None`` to remove the embed.
|
Could be ``None`` to remove the embed.
|
||||||
|
embeds: List[:class:`Embed`]
|
||||||
|
The new embeds to replace the original with. Must be a maximum of 10.
|
||||||
|
To remove all embeds ``[]`` should be passed.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
attachments: List[:class:`Attachment`]
|
attachments: List[:class:`Attachment`]
|
||||||
A list of attachments to keep in the message. If ``[]`` is passed
|
A list of attachments to keep in the message. If ``[]`` is passed
|
||||||
then all attachments are removed.
|
then all attachments are removed.
|
||||||
@@ -1156,8 +1257,6 @@ class Message(Hashable):
|
|||||||
The updated view to update this message with. If ``None`` is passed then
|
The updated view to update this message with. If ``None`` is passed then
|
||||||
the view is removed.
|
the view is removed.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@@ -1165,70 +1264,55 @@ class Message(Hashable):
|
|||||||
Forbidden
|
Forbidden
|
||||||
Tried to suppress a message without permissions or
|
Tried to suppress a message without permissions or
|
||||||
edited a message's content or embed that isn't yours.
|
edited a message's content or embed that isn't yours.
|
||||||
|
~discord.InvalidArgument
|
||||||
|
You specified both ``embed`` and ``embeds``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
payload: Dict[str, Any] = {}
|
||||||
content = fields['content']
|
if content is not MISSING:
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if content is not None:
|
if content is not None:
|
||||||
fields['content'] = str(content)
|
payload['content'] = str(content)
|
||||||
|
|
||||||
try:
|
|
||||||
embed = fields['embed']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
if embed is not None:
|
payload['content'] = None
|
||||||
fields['embed'] = embed.to_dict()
|
|
||||||
|
|
||||||
try:
|
if embed is not MISSING and embeds is not MISSING:
|
||||||
suppress = fields.pop('suppress')
|
raise InvalidArgument('cannot pass both embed and embeds parameter to edit()')
|
||||||
except KeyError:
|
|
||||||
pass
|
if embed is not MISSING:
|
||||||
|
if embed is None:
|
||||||
|
payload['embeds'] = []
|
||||||
else:
|
else:
|
||||||
|
payload['embeds'] = [embed.to_dict()]
|
||||||
|
elif embeds is not MISSING:
|
||||||
|
payload['embeds'] = [e.to_dict() for e in embeds]
|
||||||
|
|
||||||
|
if suppress is not MISSING:
|
||||||
flags = MessageFlags._from_value(self.flags.value)
|
flags = MessageFlags._from_value(self.flags.value)
|
||||||
flags.suppress_embeds = suppress
|
flags.suppress_embeds = suppress
|
||||||
fields['flags'] = flags.value
|
payload['flags'] = flags.value
|
||||||
|
|
||||||
delete_after = fields.pop('delete_after', None)
|
if allowed_mentions is MISSING:
|
||||||
|
|
||||||
try:
|
|
||||||
allowed_mentions = fields.pop('allowed_mentions')
|
|
||||||
except KeyError:
|
|
||||||
if self._state.allowed_mentions is not None and self.author.id == self._state.self_id:
|
if self._state.allowed_mentions is not None and self.author.id == self._state.self_id:
|
||||||
fields['allowed_mentions'] = self._state.allowed_mentions.to_dict()
|
payload['allowed_mentions'] = self._state.allowed_mentions.to_dict()
|
||||||
else:
|
else:
|
||||||
if allowed_mentions is not None:
|
if allowed_mentions is not None:
|
||||||
if self._state.allowed_mentions is not None:
|
if self._state.allowed_mentions is not None:
|
||||||
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict()
|
payload['allowed_mentions'] = self._state.allowed_mentions.merge(allowed_mentions).to_dict()
|
||||||
else:
|
else:
|
||||||
allowed_mentions = allowed_mentions.to_dict()
|
payload['allowed_mentions'] = allowed_mentions.to_dict()
|
||||||
fields['allowed_mentions'] = allowed_mentions
|
|
||||||
|
|
||||||
try:
|
if attachments is not MISSING:
|
||||||
attachments = fields.pop('attachments')
|
payload['attachments'] = [a.to_dict() for a in attachments]
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
fields['attachments'] = [a.to_dict() for a in attachments]
|
|
||||||
|
|
||||||
try:
|
if view is not MISSING:
|
||||||
view = fields.pop('view')
|
|
||||||
except KeyError:
|
|
||||||
# To check for the view afterwards
|
|
||||||
view = None
|
|
||||||
else:
|
|
||||||
self._state.prevent_view_updates_for(self.id)
|
self._state.prevent_view_updates_for(self.id)
|
||||||
if view:
|
if view:
|
||||||
fields['components'] = view.to_components()
|
payload['components'] = view.to_components()
|
||||||
else:
|
else:
|
||||||
fields['components'] = []
|
payload['components'] = []
|
||||||
|
|
||||||
if fields:
|
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, **fields)
|
message = Message(state=self._state, channel=self.channel, data=data)
|
||||||
self._update(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)
|
||||||
@@ -1236,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|
|
||||||
|
|
||||||
@@ -1430,44 +1516,51 @@ class Message(Hashable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.clear_reactions(self.channel.id, self.id)
|
await self._state.http.clear_reactions(self.channel.id, self.id)
|
||||||
|
|
||||||
async def start_thread(self, *, name: str, auto_archive_duration: ThreadArchiveDuration = 1440) -> Thread:
|
async def create_thread(self, *, name: str, auto_archive_duration: ThreadArchiveDuration = MISSING) -> Thread:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Starts 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 start 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`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The name of the thread.
|
The name of the thread.
|
||||||
auto_archive_duration: :class:`int`
|
auto_archive_duration: :class:`int`
|
||||||
The duration in minutes before a thread is automatically archived for inactivity.
|
The duration in minutes before a thread is automatically archived for inactivity.
|
||||||
Defaults to ``1440`` or 24 hours.
|
If not provided, the channel's default auto archive duration is used.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
Forbidden
|
Forbidden
|
||||||
You do not have permissions to start a thread.
|
You do not have permissions to create a thread.
|
||||||
HTTPException
|
HTTPException
|
||||||
Starting the thread failed.
|
Creating the thread failed.
|
||||||
InvalidArgument
|
InvalidArgument
|
||||||
This message does not have guild info attached.
|
This message does not have guild info attached.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`.Thread`
|
||||||
|
The created thread.
|
||||||
"""
|
"""
|
||||||
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.')
|
||||||
|
|
||||||
data = await self._state.http.start_public_thread(
|
default_auto_archive_duration: ThreadArchiveDuration = getattr(self.channel, 'default_auto_archive_duration', 1440)
|
||||||
|
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,
|
auto_archive_duration=auto_archive_duration or default_auto_archive_duration,
|
||||||
type=ChannelType.public_thread.value,
|
|
||||||
)
|
)
|
||||||
return Thread(guild=self.guild, 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|
|
||||||
@@ -1533,8 +1626,11 @@ class PartialMessage(Hashable):
|
|||||||
a message and channel ID are present.
|
a message and channel ID are present.
|
||||||
|
|
||||||
There are two ways to construct this class. The first one is through
|
There are two ways to construct this class. The first one is through
|
||||||
the constructor itself, and the second is via
|
the constructor itself, and the second is via the following:
|
||||||
:meth:`TextChannel.get_partial_message` or :meth:`DMChannel.get_partial_message`.
|
|
||||||
|
- :meth:`TextChannel.get_partial_message`
|
||||||
|
- :meth:`Thread.get_partial_message`
|
||||||
|
- :meth:`DMChannel.get_partial_message`
|
||||||
|
|
||||||
Note that this class is trimmed down and has no rich attributes.
|
Note that this class is trimmed down and has no rich attributes.
|
||||||
|
|
||||||
@@ -1554,9 +1650,13 @@ 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:`DMChannel`]
|
channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`]
|
||||||
The channel associated with this partial message.
|
The channel associated with this partial message.
|
||||||
id: :class:`int`
|
id: :class:`int`
|
||||||
The message ID.
|
The message ID.
|
||||||
@@ -1577,13 +1677,20 @@ class PartialMessage(Hashable):
|
|||||||
to_reference = Message.to_reference
|
to_reference = Message.to_reference
|
||||||
to_message_reference_dict = Message.to_message_reference_dict
|
to_message_reference_dict = Message.to_message_reference_dict
|
||||||
|
|
||||||
def __init__(self, *, channel: Union[TextChannel, DMChannel], id: int):
|
def __init__(self, *, channel: PartialMessageableChannel, id: int):
|
||||||
if channel.type not in (ChannelType.text, ChannelType.news, ChannelType.private):
|
if channel.type not in (
|
||||||
raise TypeError(f'Expected TextChannel or DMChannel not {type(channel)!r}')
|
ChannelType.text,
|
||||||
|
ChannelType.news,
|
||||||
|
ChannelType.private,
|
||||||
|
ChannelType.news_thread,
|
||||||
|
ChannelType.public_thread,
|
||||||
|
ChannelType.private_thread,
|
||||||
|
):
|
||||||
|
raise TypeError(f'Expected TextChannel, DMChannel or Thread not {type(channel)!r}')
|
||||||
|
|
||||||
self.channel = channel
|
self.channel: PartialMessageableChannel = channel
|
||||||
self._state = channel._state
|
self._state: ConnectionState = channel._state
|
||||||
self.id = id
|
self.id: int = id
|
||||||
|
|
||||||
def _update(self, data) -> None:
|
def _update(self, data) -> None:
|
||||||
# This is used for duck typing purposes.
|
# This is used for duck typing purposes.
|
||||||
@@ -1703,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:
|
||||||
@@ -1741,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)
|
||||||
|
|||||||
@@ -22,24 +22,20 @@ 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 TypeVar
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'EqualityComparable',
|
'EqualityComparable',
|
||||||
'Hashable',
|
'Hashable',
|
||||||
)
|
)
|
||||||
|
|
||||||
E = TypeVar('E', bound='EqualityComparable')
|
|
||||||
|
|
||||||
class EqualityComparable:
|
class EqualityComparable:
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
def __eq__(self: E, other: E) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
return isinstance(other, self.__class__) and other.id == self.id
|
return isinstance(other, self.__class__) and other.id == self.id
|
||||||
|
|
||||||
def __ne__(self: E, other: E) -> bool:
|
def __ne__(self, other: object) -> bool:
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
return other.id != self.id
|
return other.id != self.id
|
||||||
return True
|
return True
|
||||||
@@ -47,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
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
125
discord/opus.py
125
discord/opus.py
@@ -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
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, Optional, TYPE_CHECKING, Type, TypeVar
|
from typing import Any, Dict, Optional, TYPE_CHECKING, Type, TypeVar, Union
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .asset import Asset, AssetMixin
|
from .asset import Asset, AssetMixin
|
||||||
@@ -104,11 +104,11 @@ class PartialEmoji(_EmojiTag, AssetMixin):
|
|||||||
self._state: Optional[ConnectionState] = None
|
self._state: Optional[ConnectionState] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: Type[PE], data: Dict[str, Any]) -> PE:
|
def from_dict(cls: Type[PE], data: Union[PartialEmojiPayload, Dict[str, Any]]) -> PE:
|
||||||
return cls(
|
return cls(
|
||||||
animated=data.get('animated', False),
|
animated=data.get('animated', False),
|
||||||
id=utils._get_as_snowflake(data, 'id'),
|
id=utils._get_as_snowflake(data, 'id'),
|
||||||
name=data.get('name', ''),
|
name=data.get('name') or '',
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -22,6 +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
|
||||||
|
|
||||||
|
from typing import Callable, Any, ClassVar, Dict, Iterator, Set, TYPE_CHECKING, Tuple, Type, TypeVar, Optional
|
||||||
from .flags import BaseFlags, flag_value, fill_with_flags, alias_flag_value
|
from .flags import BaseFlags, flag_value, fill_with_flags, alias_flag_value
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@@ -32,15 +35,19 @@ __all__ = (
|
|||||||
# A permission alias works like a regular flag but is marked
|
# A permission alias works like a regular flag but is marked
|
||||||
# So the PermissionOverwrite knows to work with it
|
# So the PermissionOverwrite knows to work with it
|
||||||
class permission_alias(alias_flag_value):
|
class permission_alias(alias_flag_value):
|
||||||
pass
|
alias: str
|
||||||
|
|
||||||
def make_permission_alias(alias):
|
|
||||||
def decorator(func):
|
def make_permission_alias(alias: str) -> Callable[[Callable[[Any], int]], permission_alias]:
|
||||||
|
def decorator(func: Callable[[Any], int]) -> permission_alias:
|
||||||
ret = permission_alias(func)
|
ret = permission_alias(func)
|
||||||
ret.alias = alias
|
ret.alias = alias
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
P = TypeVar('P', bound='Permissions')
|
||||||
|
|
||||||
@fill_with_flags()
|
@fill_with_flags()
|
||||||
class Permissions(BaseFlags):
|
class Permissions(BaseFlags):
|
||||||
"""Wraps up the Discord permission value.
|
"""Wraps up the Discord permission value.
|
||||||
@@ -92,7 +99,7 @@ class Permissions(BaseFlags):
|
|||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __init__(self, permissions=0, **kwargs):
|
def __init__(self, permissions: int = 0, **kwargs: bool):
|
||||||
if not isinstance(permissions, int):
|
if not isinstance(permissions, int):
|
||||||
raise TypeError(f'Expected int parameter, received {permissions.__class__.__name__} instead.')
|
raise TypeError(f'Expected int parameter, received {permissions.__class__.__name__} instead.')
|
||||||
|
|
||||||
@@ -102,25 +109,25 @@ class Permissions(BaseFlags):
|
|||||||
raise TypeError(f'{key!r} is not a valid permission name.')
|
raise TypeError(f'{key!r} is not a valid permission name.')
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def is_subset(self, other):
|
def is_subset(self, other: Permissions) -> bool:
|
||||||
"""Returns ``True`` if self has the same or fewer permissions as other."""
|
"""Returns ``True`` if self has the same or fewer permissions as other."""
|
||||||
if isinstance(other, Permissions):
|
if isinstance(other, Permissions):
|
||||||
return (self.value & other.value) == self.value
|
return (self.value & other.value) == self.value
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}")
|
raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||||
|
|
||||||
def is_superset(self, other):
|
def is_superset(self, other: Permissions) -> bool:
|
||||||
"""Returns ``True`` if self has the same or more permissions as other."""
|
"""Returns ``True`` if self has the same or more permissions as other."""
|
||||||
if isinstance(other, Permissions):
|
if isinstance(other, Permissions):
|
||||||
return (self.value | other.value) == self.value
|
return (self.value | other.value) == self.value
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}")
|
raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||||
|
|
||||||
def is_strict_subset(self, other):
|
def is_strict_subset(self, other: Permissions) -> bool:
|
||||||
"""Returns ``True`` if the permissions on other are a strict subset of those on self."""
|
"""Returns ``True`` if the permissions on other are a strict subset of those on self."""
|
||||||
return self.is_subset(other) and self != other
|
return self.is_subset(other) and self != other
|
||||||
|
|
||||||
def is_strict_superset(self, other):
|
def is_strict_superset(self, other: Permissions) -> bool:
|
||||||
"""Returns ``True`` if the permissions on other are a strict superset of those on self."""
|
"""Returns ``True`` if the permissions on other are a strict superset of those on self."""
|
||||||
return self.is_superset(other) and self != other
|
return self.is_superset(other) and self != other
|
||||||
|
|
||||||
@@ -130,20 +137,20 @@ class Permissions(BaseFlags):
|
|||||||
__gt__ = is_strict_superset
|
__gt__ = is_strict_superset
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def none(cls):
|
def none(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
permissions set to ``False``."""
|
permissions set to ``False``."""
|
||||||
return cls(0)
|
return cls(0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all(cls):
|
def all(cls: Type[P]) -> P:
|
||||||
"""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(0b111111111111111111111111111111111111)
|
return cls(0b111111111111111111111111111111111111111)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all_channel(cls):
|
def all_channel(cls: Type[P]) -> P:
|
||||||
"""A :class:`Permissions` with all channel-specific permissions set to
|
"""A :class:`Permissions` with all channel-specific permissions set to
|
||||||
``True`` and the guild-specific ones set to ``False``. The guild-specific
|
``True`` and the guild-specific ones set to ``False``. The guild-specific
|
||||||
permissions are currently:
|
permissions are currently:
|
||||||
@@ -160,11 +167,16 @@ class Permissions(BaseFlags):
|
|||||||
|
|
||||||
.. versionchanged:: 1.7
|
.. versionchanged:: 1.7
|
||||||
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
|
||||||
|
Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`,
|
||||||
|
:attr:`use_external_stickers`, :attr:`send_messages_in_threads` and
|
||||||
|
:attr:`request_to_speak` permissions.
|
||||||
"""
|
"""
|
||||||
return cls(0b10110011111101111111111101010001)
|
return cls(0b111110110110011111101111111111101010001)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def general(cls):
|
def general(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"General" permissions from the official Discord UI set to ``True``.
|
"General" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
@@ -177,7 +189,7 @@ class Permissions(BaseFlags):
|
|||||||
return cls(0b01110000000010000000010010110000)
|
return cls(0b01110000000010000000010010110000)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def membership(cls):
|
def membership(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Membership" permissions from the official Discord UI set to ``True``.
|
"Membership" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
@@ -186,24 +198,28 @@ class Permissions(BaseFlags):
|
|||||||
return cls(0b00001100000000000000000000000111)
|
return cls(0b00001100000000000000000000000111)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def text(cls):
|
def text(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Text" permissions from the official Discord UI set to ``True``.
|
"Text" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
.. versionchanged:: 1.7
|
||||||
Permission :attr:`read_messages` is no longer part of the text permissions.
|
Permission :attr:`read_messages` is no longer part of the text permissions.
|
||||||
Added :attr:`use_slash_commands` permission.
|
Added :attr:`use_slash_commands` permission.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Added :attr:`create_public_threads`, :attr:`create_private_threads`, :attr:`manage_threads`,
|
||||||
|
:attr:`send_messages_in_threads` and :attr:`use_external_stickers` permissions.
|
||||||
"""
|
"""
|
||||||
return cls(0b10000000000001111111100001000000)
|
return cls(0b111110010000000000001111111100001000000)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def voice(cls):
|
def voice(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Voice" permissions from the official Discord UI set to ``True``."""
|
"Voice" permissions from the official Discord UI set to ``True``."""
|
||||||
return cls(0b00000011111100000000001100000000)
|
return cls(0b00000011111100000000001100000000)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage(cls):
|
def stage(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Stage Channel" permissions from the official Discord UI set to ``True``.
|
"Stage Channel" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
@@ -212,7 +228,7 @@ class Permissions(BaseFlags):
|
|||||||
return cls(1 << 32)
|
return cls(1 << 32)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_moderator(cls):
|
def stage_moderator(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Stage Moderator" permissions from the official Discord UI set to ``True``.
|
"Stage Moderator" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
@@ -221,7 +237,7 @@ class Permissions(BaseFlags):
|
|||||||
return cls(0b100000001010000000000000000000000)
|
return cls(0b100000001010000000000000000000000)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def advanced(cls):
|
def advanced(cls: Type[P]) -> P:
|
||||||
"""A factory method that creates a :class:`Permissions` with all
|
"""A factory method that creates a :class:`Permissions` with all
|
||||||
"Advanced" permissions from the official Discord UI set to ``True``.
|
"Advanced" permissions from the official Discord UI set to ``True``.
|
||||||
|
|
||||||
@@ -229,7 +245,7 @@ class Permissions(BaseFlags):
|
|||||||
"""
|
"""
|
||||||
return cls(1 << 3)
|
return cls(1 << 3)
|
||||||
|
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs: bool) -> None:
|
||||||
r"""Bulk updates this permission object.
|
r"""Bulk updates this permission object.
|
||||||
|
|
||||||
Allows you to set multiple attributes by using keyword
|
Allows you to set multiple attributes by using keyword
|
||||||
@@ -245,7 +261,7 @@ class Permissions(BaseFlags):
|
|||||||
if key in self.VALID_FLAGS:
|
if key in self.VALID_FLAGS:
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def handle_overwrite(self, allow, deny):
|
def handle_overwrite(self, allow: int, deny: int) -> None:
|
||||||
# Basically this is what's happening here.
|
# Basically this is what's happening here.
|
||||||
# We have an original bit array, e.g. 1010
|
# We have an original bit array, e.g. 1010
|
||||||
# Then we have another bit array that is 'denied', e.g. 1111
|
# Then we have another bit array that is 'denied', e.g. 1111
|
||||||
@@ -261,22 +277,22 @@ class Permissions(BaseFlags):
|
|||||||
self.value = (self.value & ~deny) | allow
|
self.value = (self.value & ~deny) | allow
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def create_instant_invite(self):
|
def create_instant_invite(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if the user can create instant invites."""
|
""":class:`bool`: Returns ``True`` if the user can create instant invites."""
|
||||||
return 1 << 0
|
return 1 << 0
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def kick_members(self):
|
def kick_members(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if the user can kick users from the guild."""
|
""":class:`bool`: Returns ``True`` if the user can kick users from the guild."""
|
||||||
return 1 << 1
|
return 1 << 1
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def ban_members(self):
|
def ban_members(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can ban users from the guild."""
|
""":class:`bool`: Returns ``True`` if a user can ban users from the guild."""
|
||||||
return 1 << 2
|
return 1 << 2
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def administrator(self):
|
def administrator(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user is an administrator. This role overrides all other permissions.
|
""":class:`bool`: Returns ``True`` if a user is an administrator. This role overrides all other permissions.
|
||||||
|
|
||||||
This also bypasses all channel-specific overrides.
|
This also bypasses all channel-specific overrides.
|
||||||
@@ -284,44 +300,44 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 3
|
return 1 << 3
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_channels(self):
|
def manage_channels(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can edit, delete, or create channels in the guild.
|
""":class:`bool`: Returns ``True`` if a user can edit, delete, or create channels in the guild.
|
||||||
|
|
||||||
This also corresponds to the "Manage Channel" channel-specific override."""
|
This also corresponds to the "Manage Channel" channel-specific override."""
|
||||||
return 1 << 4
|
return 1 << 4
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_guild(self):
|
def manage_guild(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can edit guild properties."""
|
""":class:`bool`: Returns ``True`` if a user can edit guild properties."""
|
||||||
return 1 << 5
|
return 1 << 5
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def add_reactions(self):
|
def add_reactions(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can add reactions to messages."""
|
""":class:`bool`: Returns ``True`` if a user can add reactions to messages."""
|
||||||
return 1 << 6
|
return 1 << 6
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def view_audit_log(self):
|
def view_audit_log(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can view the guild's audit log."""
|
""":class:`bool`: Returns ``True`` if a user can view the guild's audit log."""
|
||||||
return 1 << 7
|
return 1 << 7
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def priority_speaker(self):
|
def priority_speaker(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can be more easily heard while talking."""
|
""":class:`bool`: Returns ``True`` if a user can be more easily heard while talking."""
|
||||||
return 1 << 8
|
return 1 << 8
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def stream(self):
|
def stream(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can stream in a voice channel."""
|
""":class:`bool`: Returns ``True`` if a user can stream in a voice channel."""
|
||||||
return 1 << 9
|
return 1 << 9
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def read_messages(self):
|
def read_messages(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can read messages from all or specific text channels."""
|
""":class:`bool`: Returns ``True`` if a user can read messages from all or specific text channels."""
|
||||||
return 1 << 10
|
return 1 << 10
|
||||||
|
|
||||||
@make_permission_alias('read_messages')
|
@make_permission_alias('read_messages')
|
||||||
def view_channel(self):
|
def view_channel(self) -> int:
|
||||||
""":class:`bool`: An alias for :attr:`read_messages`.
|
""":class:`bool`: An alias for :attr:`read_messages`.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
@@ -329,17 +345,17 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 10
|
return 1 << 10
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def send_messages(self):
|
def send_messages(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can send messages from all or specific text channels."""
|
""":class:`bool`: Returns ``True`` if a user can send messages from all or specific text channels."""
|
||||||
return 1 << 11
|
return 1 << 11
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def send_tts_messages(self):
|
def send_tts_messages(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can send TTS messages from all or specific text channels."""
|
""":class:`bool`: Returns ``True`` if a user can send TTS messages from all or specific text channels."""
|
||||||
return 1 << 12
|
return 1 << 12
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_messages(self):
|
def manage_messages(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can delete or pin messages in a text channel.
|
""":class:`bool`: Returns ``True`` if a user can delete or pin messages in a text channel.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -349,32 +365,32 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 13
|
return 1 << 13
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def embed_links(self):
|
def embed_links(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user's messages will automatically be embedded by Discord."""
|
""":class:`bool`: Returns ``True`` if a user's messages will automatically be embedded by Discord."""
|
||||||
return 1 << 14
|
return 1 << 14
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def attach_files(self):
|
def attach_files(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can send files in their messages."""
|
""":class:`bool`: Returns ``True`` if a user can send files in their messages."""
|
||||||
return 1 << 15
|
return 1 << 15
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def read_message_history(self):
|
def read_message_history(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can read a text channel's previous messages."""
|
""":class:`bool`: Returns ``True`` if a user can read a text channel's previous messages."""
|
||||||
return 1 << 16
|
return 1 << 16
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def mention_everyone(self):
|
def mention_everyone(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user's @everyone or @here will mention everyone in the text channel."""
|
""":class:`bool`: Returns ``True`` if a user's @everyone or @here will mention everyone in the text channel."""
|
||||||
return 1 << 17
|
return 1 << 17
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def external_emojis(self):
|
def external_emojis(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can use emojis from other guilds."""
|
""":class:`bool`: Returns ``True`` if a user can use emojis from other guilds."""
|
||||||
return 1 << 18
|
return 1 << 18
|
||||||
|
|
||||||
@make_permission_alias('external_emojis')
|
@make_permission_alias('external_emojis')
|
||||||
def use_external_emojis(self):
|
def use_external_emojis(self) -> int:
|
||||||
""":class:`bool`: An alias for :attr:`external_emojis`.
|
""":class:`bool`: An alias for :attr:`external_emojis`.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
@@ -382,7 +398,7 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 18
|
return 1 << 18
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def view_guild_insights(self):
|
def view_guild_insights(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can view the guild's insights.
|
""":class:`bool`: Returns ``True`` if a user can view the guild's insights.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
@@ -390,47 +406,47 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 19
|
return 1 << 19
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def connect(self):
|
def connect(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can connect to a voice channel."""
|
""":class:`bool`: Returns ``True`` if a user can connect to a voice channel."""
|
||||||
return 1 << 20
|
return 1 << 20
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def speak(self):
|
def speak(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can speak in a voice channel."""
|
""":class:`bool`: Returns ``True`` if a user can speak in a voice channel."""
|
||||||
return 1 << 21
|
return 1 << 21
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def mute_members(self):
|
def mute_members(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can mute other users."""
|
""":class:`bool`: Returns ``True`` if a user can mute other users."""
|
||||||
return 1 << 22
|
return 1 << 22
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def deafen_members(self):
|
def deafen_members(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can deafen other users."""
|
""":class:`bool`: Returns ``True`` if a user can deafen other users."""
|
||||||
return 1 << 23
|
return 1 << 23
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def move_members(self):
|
def move_members(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can move users between other voice channels."""
|
""":class:`bool`: Returns ``True`` if a user can move users between other voice channels."""
|
||||||
return 1 << 24
|
return 1 << 24
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def use_voice_activation(self):
|
def use_voice_activation(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can use voice activation in voice channels."""
|
""":class:`bool`: Returns ``True`` if a user can use voice activation in voice channels."""
|
||||||
return 1 << 25
|
return 1 << 25
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def change_nickname(self):
|
def change_nickname(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can change their nickname in the guild."""
|
""":class:`bool`: Returns ``True`` if a user can change their nickname in the guild."""
|
||||||
return 1 << 26
|
return 1 << 26
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_nicknames(self):
|
def manage_nicknames(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can change other user's nickname in the guild."""
|
""":class:`bool`: Returns ``True`` if a user can change other user's nickname in the guild."""
|
||||||
return 1 << 27
|
return 1 << 27
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_roles(self):
|
def manage_roles(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can create or edit roles less than their role's position.
|
""":class:`bool`: Returns ``True`` if a user can create or edit roles less than their role's position.
|
||||||
|
|
||||||
This also corresponds to the "Manage Permissions" channel-specific override.
|
This also corresponds to the "Manage Permissions" channel-specific override.
|
||||||
@@ -438,7 +454,7 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 28
|
return 1 << 28
|
||||||
|
|
||||||
@make_permission_alias('manage_roles')
|
@make_permission_alias('manage_roles')
|
||||||
def manage_permissions(self):
|
def manage_permissions(self) -> int:
|
||||||
""":class:`bool`: An alias for :attr:`manage_roles`.
|
""":class:`bool`: An alias for :attr:`manage_roles`.
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
@@ -446,17 +462,25 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 28
|
return 1 << 28
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_webhooks(self):
|
def manage_webhooks(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete webhooks."""
|
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete webhooks."""
|
||||||
return 1 << 29
|
return 1 << 29
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_emojis(self):
|
def manage_emojis(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis."""
|
""":class:`bool`: Returns ``True`` if a user can create, edit, or delete emojis."""
|
||||||
return 1 << 30
|
return 1 << 30
|
||||||
|
|
||||||
|
@make_permission_alias('manage_emojis')
|
||||||
|
def manage_emojis_and_stickers(self) -> int:
|
||||||
|
""":class:`bool`: An alias for :attr:`manage_emojis`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
return 1 << 30
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def use_slash_commands(self):
|
def use_slash_commands(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can use slash commands.
|
""":class:`bool`: Returns ``True`` if a user can use slash commands.
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
@@ -464,7 +488,7 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 31
|
return 1 << 31
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def request_to_speak(self):
|
def request_to_speak(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can request to speak in a stage channel.
|
""":class:`bool`: Returns ``True`` if a user can request to speak in a stage channel.
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
@@ -472,7 +496,7 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 32
|
return 1 << 32
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_events(self):
|
def manage_events(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can manage guild events.
|
""":class:`bool`: Returns ``True`` if a user can manage guild events.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
@@ -480,7 +504,7 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 33
|
return 1 << 33
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def manage_threads(self):
|
def manage_threads(self) -> int:
|
||||||
""":class:`bool`: Returns ``True`` if a user can manage threads.
|
""":class:`bool`: Returns ``True`` if a user can manage threads.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
@@ -488,23 +512,48 @@ class Permissions(BaseFlags):
|
|||||||
return 1 << 34
|
return 1 << 34
|
||||||
|
|
||||||
@flag_value
|
@flag_value
|
||||||
def use_threads(self):
|
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):
|
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
|
||||||
"""
|
"""
|
||||||
return 1 << 36
|
return 1 << 36
|
||||||
|
|
||||||
|
@flag_value
|
||||||
|
def external_stickers(self) -> int:
|
||||||
|
""":class:`bool`: Returns ``True`` if a user can use stickers from other guilds.
|
||||||
|
|
||||||
def augment_from_permissions(cls):
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
return 1 << 37
|
||||||
|
|
||||||
|
@make_permission_alias('external_stickers')
|
||||||
|
def use_external_stickers(self) -> int:
|
||||||
|
""":class:`bool`: An alias for :attr:`external_stickers`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
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')
|
||||||
|
|
||||||
|
def _augment_from_permissions(cls):
|
||||||
cls.VALID_NAMES = set(Permissions.VALID_FLAGS)
|
cls.VALID_NAMES = set(Permissions.VALID_FLAGS)
|
||||||
aliases = set()
|
aliases = set()
|
||||||
|
|
||||||
@@ -521,6 +570,7 @@ def augment_from_permissions(cls):
|
|||||||
# god bless Python
|
# god bless Python
|
||||||
def getter(self, x=key):
|
def getter(self, x=key):
|
||||||
return self._values.get(x)
|
return self._values.get(x)
|
||||||
|
|
||||||
def setter(self, value, x=key):
|
def setter(self, value, x=key):
|
||||||
self._set(x, value)
|
self._set(x, value)
|
||||||
|
|
||||||
@@ -530,7 +580,8 @@ def augment_from_permissions(cls):
|
|||||||
cls.PURE_FLAGS = cls.VALID_NAMES - aliases
|
cls.PURE_FLAGS = cls.VALID_NAMES - aliases
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@augment_from_permissions
|
|
||||||
|
@_augment_from_permissions
|
||||||
class PermissionOverwrite:
|
class PermissionOverwrite:
|
||||||
r"""A type that is used to represent a channel specific permission.
|
r"""A type that is used to represent a channel specific permission.
|
||||||
|
|
||||||
@@ -565,8 +616,57 @@ class PermissionOverwrite:
|
|||||||
|
|
||||||
__slots__ = ('_values',)
|
__slots__ = ('_values',)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
if TYPE_CHECKING:
|
||||||
self._values = {}
|
VALID_NAMES: ClassVar[Set[str]]
|
||||||
|
PURE_FLAGS: ClassVar[Set[str]]
|
||||||
|
# I wish I didn't have to do this
|
||||||
|
create_instant_invite: Optional[bool]
|
||||||
|
kick_members: Optional[bool]
|
||||||
|
ban_members: Optional[bool]
|
||||||
|
administrator: Optional[bool]
|
||||||
|
manage_channels: Optional[bool]
|
||||||
|
manage_guild: Optional[bool]
|
||||||
|
add_reactions: Optional[bool]
|
||||||
|
view_audit_log: Optional[bool]
|
||||||
|
priority_speaker: Optional[bool]
|
||||||
|
stream: Optional[bool]
|
||||||
|
read_messages: Optional[bool]
|
||||||
|
view_channel: Optional[bool]
|
||||||
|
send_messages: Optional[bool]
|
||||||
|
send_tts_messages: Optional[bool]
|
||||||
|
manage_messages: Optional[bool]
|
||||||
|
embed_links: Optional[bool]
|
||||||
|
attach_files: Optional[bool]
|
||||||
|
read_message_history: Optional[bool]
|
||||||
|
mention_everyone: Optional[bool]
|
||||||
|
external_emojis: Optional[bool]
|
||||||
|
use_external_emojis: Optional[bool]
|
||||||
|
view_guild_insights: Optional[bool]
|
||||||
|
connect: Optional[bool]
|
||||||
|
speak: Optional[bool]
|
||||||
|
mute_members: Optional[bool]
|
||||||
|
deafen_members: Optional[bool]
|
||||||
|
move_members: Optional[bool]
|
||||||
|
use_voice_activation: Optional[bool]
|
||||||
|
change_nickname: Optional[bool]
|
||||||
|
manage_nicknames: Optional[bool]
|
||||||
|
manage_roles: Optional[bool]
|
||||||
|
manage_permissions: Optional[bool]
|
||||||
|
manage_webhooks: Optional[bool]
|
||||||
|
manage_emojis: Optional[bool]
|
||||||
|
manage_emojis_and_stickers: Optional[bool]
|
||||||
|
use_slash_commands: Optional[bool]
|
||||||
|
request_to_speak: Optional[bool]
|
||||||
|
manage_events: Optional[bool]
|
||||||
|
manage_threads: Optional[bool]
|
||||||
|
create_public_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]):
|
||||||
|
self._values: Dict[str, Optional[bool]] = {}
|
||||||
|
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in self.VALID_NAMES:
|
if key not in self.VALID_NAMES:
|
||||||
@@ -574,10 +674,10 @@ class PermissionOverwrite:
|
|||||||
|
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return isinstance(other, PermissionOverwrite) and self._values == other._values
|
return isinstance(other, PermissionOverwrite) and self._values == other._values
|
||||||
|
|
||||||
def _set(self, key, value):
|
def _set(self, key: str, value: Optional[bool]) -> None:
|
||||||
if value not in (True, None, False):
|
if value not in (True, None, False):
|
||||||
raise TypeError(f'Expected bool or NoneType, received {value.__class__.__name__}')
|
raise TypeError(f'Expected bool or NoneType, received {value.__class__.__name__}')
|
||||||
|
|
||||||
@@ -586,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()
|
||||||
@@ -601,7 +701,7 @@ class PermissionOverwrite:
|
|||||||
return allow, deny
|
return allow, deny
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pair(cls, allow, deny):
|
def from_pair(cls: Type[PO], allow: Permissions, deny: Permissions) -> PO:
|
||||||
"""Creates an overwrite from an allow/deny pair of :class:`Permissions`."""
|
"""Creates an overwrite from an allow/deny pair of :class:`Permissions`."""
|
||||||
ret = cls()
|
ret = cls()
|
||||||
for key, value in allow:
|
for key, value in allow:
|
||||||
@@ -614,7 +714,7 @@ class PermissionOverwrite:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def is_empty(self):
|
def is_empty(self) -> bool:
|
||||||
"""Checks if the permission overwrite is currently empty.
|
"""Checks if the permission overwrite is currently empty.
|
||||||
|
|
||||||
An empty permission overwrite is one that has no overwrites set
|
An empty permission overwrite is one that has no overwrites set
|
||||||
@@ -627,7 +727,7 @@ class PermissionOverwrite:
|
|||||||
"""
|
"""
|
||||||
return len(self._values) == 0
|
return len(self._values) == 0
|
||||||
|
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs: bool) -> None:
|
||||||
r"""Bulk updates this permission overwrite object.
|
r"""Bulk updates this permission overwrite object.
|
||||||
|
|
||||||
Allows you to set multiple attributes by using keyword
|
Allows you to set multiple attributes by using keyword
|
||||||
@@ -645,6 +745,6 @@ class PermissionOverwrite:
|
|||||||
|
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> Iterator[Tuple[str, Optional[bool]]]:
|
||||||
for key in self.PURE_FLAGS:
|
for key in self.PURE_FLAGS:
|
||||||
yield key, self._values.get(key)
|
yield key, self._values.get(key)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
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 threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
@@ -33,12 +34,23 @@ import time
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
import io
|
||||||
|
|
||||||
|
from typing import Any, Callable, Generic, IO, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union
|
||||||
|
|
||||||
from .errors import ClientException
|
from .errors import ClientException
|
||||||
from .opus import Encoder as OpusEncoder
|
from .opus import Encoder as OpusEncoder
|
||||||
from .oggparse import OggStream
|
from .oggparse import OggStream
|
||||||
|
from .utils import MISSING
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
if TYPE_CHECKING:
|
||||||
|
from .voice_client import VoiceClient
|
||||||
|
|
||||||
|
|
||||||
|
AT = TypeVar('AT', bound='AudioSource')
|
||||||
|
FT = TypeVar('FT', bound='FFmpegOpusAudio')
|
||||||
|
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AudioSource',
|
'AudioSource',
|
||||||
@@ -49,6 +61,8 @@ __all__ = (
|
|||||||
'PCMVolumeTransformer',
|
'PCMVolumeTransformer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CREATE_NO_WINDOW: int
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
CREATE_NO_WINDOW = 0
|
CREATE_NO_WINDOW = 0
|
||||||
else:
|
else:
|
||||||
@@ -65,7 +79,7 @@ class AudioSource:
|
|||||||
The audio source reads are done in a separate thread.
|
The audio source reads are done in a separate thread.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> bytes:
|
||||||
"""Reads 20ms worth of audio.
|
"""Reads 20ms worth of audio.
|
||||||
|
|
||||||
Subclasses must implement this.
|
Subclasses must implement this.
|
||||||
@@ -85,11 +99,11 @@ class AudioSource:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def is_opus(self):
|
def is_opus(self) -> bool:
|
||||||
"""Checks if the audio source is already encoded in Opus."""
|
"""Checks if the audio source is already encoded in Opus."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self) -> None:
|
||||||
"""Called when clean-up is needed to be done.
|
"""Called when clean-up is needed to be done.
|
||||||
|
|
||||||
Useful for clearing buffer data or processes after
|
Useful for clearing buffer data or processes after
|
||||||
@@ -97,7 +111,7 @@ class AudioSource:
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self) -> None:
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
|
||||||
class PCMAudio(AudioSource):
|
class PCMAudio(AudioSource):
|
||||||
@@ -108,10 +122,10 @@ class PCMAudio(AudioSource):
|
|||||||
stream: :term:`py:file object`
|
stream: :term:`py:file object`
|
||||||
A file-like object that reads byte data representing raw PCM.
|
A file-like object that reads byte data representing raw PCM.
|
||||||
"""
|
"""
|
||||||
def __init__(self, stream):
|
def __init__(self, stream: io.BufferedIOBase) -> None:
|
||||||
self.stream = stream
|
self.stream: io.BufferedIOBase = stream
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> bytes:
|
||||||
ret = self.stream.read(OpusEncoder.FRAME_SIZE)
|
ret = self.stream.read(OpusEncoder.FRAME_SIZE)
|
||||||
if len(ret) != OpusEncoder.FRAME_SIZE:
|
if len(ret) != OpusEncoder.FRAME_SIZE:
|
||||||
return b''
|
return b''
|
||||||
@@ -126,17 +140,27 @@ class FFmpegAudio(AudioSource):
|
|||||||
.. versionadded:: 1.3
|
.. versionadded:: 1.3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, source, *, executable='ffmpeg', args, **subprocess_kwargs):
|
def __init__(self, source: Union[str, io.BufferedIOBase], *, executable: str = 'ffmpeg', args: Any, **subprocess_kwargs: Any):
|
||||||
self._process = self._stdout = None
|
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 = self._spawn_process(args, **kwargs)
|
self._process: subprocess.Popen = self._spawn_process(args, **kwargs)
|
||||||
self._stdout = self._process.stdout
|
self._stdout: IO[bytes] = self._process.stdout # type: ignore
|
||||||
|
self._stdin: Optional[IO[Bytes]] = None
|
||||||
|
self._pipe_thread: Optional[threading.Thread] = None
|
||||||
|
|
||||||
def _spawn_process(self, args, **subprocess_kwargs):
|
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:
|
||||||
process = None
|
process = None
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs)
|
process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs)
|
||||||
@@ -148,26 +172,44 @@ class FFmpegAudio(AudioSource):
|
|||||||
else:
|
else:
|
||||||
return process
|
return process
|
||||||
|
|
||||||
def cleanup(self):
|
def _kill_process(self) -> None:
|
||||||
proc = self._process
|
proc = self._process
|
||||||
if proc is None:
|
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 = None
|
|
||||||
|
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).
|
||||||
@@ -204,9 +246,18 @@ class FFmpegPCMAudio(FFmpegAudio):
|
|||||||
The subprocess failed to be created.
|
The subprocess failed to be created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, source, *, executable='ffmpeg', pipe=False, stderr=None, before_options=None, options=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
source: Union[str, io.BufferedIOBase],
|
||||||
|
*,
|
||||||
|
executable: str = 'ffmpeg',
|
||||||
|
pipe: bool = False,
|
||||||
|
stderr: Optional[IO[str]] = None,
|
||||||
|
before_options: Optional[str] = None,
|
||||||
|
options: Optional[str] = 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))
|
||||||
@@ -222,13 +273,13 @@ class FFmpegPCMAudio(FFmpegAudio):
|
|||||||
|
|
||||||
super().__init__(source, executable=executable, args=args, **subprocess_kwargs)
|
super().__init__(source, executable=executable, args=args, **subprocess_kwargs)
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> bytes:
|
||||||
ret = self._stdout.read(OpusEncoder.FRAME_SIZE)
|
ret = self._stdout.read(OpusEncoder.FRAME_SIZE)
|
||||||
if len(ret) != OpusEncoder.FRAME_SIZE:
|
if len(ret) != OpusEncoder.FRAME_SIZE:
|
||||||
return b''
|
return b''
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def is_opus(self):
|
def is_opus(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class FFmpegOpusAudio(FFmpegAudio):
|
class FFmpegOpusAudio(FFmpegAudio):
|
||||||
@@ -292,11 +343,21 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
The subprocess failed to be created.
|
The subprocess failed to be created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, source, *, bitrate=128, codec=None, executable='ffmpeg',
|
def __init__(
|
||||||
pipe=False, stderr=None, before_options=None, options=None):
|
self,
|
||||||
|
source: Union[str, io.BufferedIOBase],
|
||||||
|
*,
|
||||||
|
bitrate: int = 128,
|
||||||
|
codec: Optional[str] = None,
|
||||||
|
executable: str = 'ffmpeg',
|
||||||
|
pipe=False,
|
||||||
|
stderr=None,
|
||||||
|
before_options=None,
|
||||||
|
options=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))
|
||||||
@@ -323,7 +384,13 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
self._packet_iter = OggStream(self._stdout).iter_packets()
|
self._packet_iter = OggStream(self._stdout).iter_packets()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_probe(cls, source, *, method=None, **kwargs):
|
async def from_probe(
|
||||||
|
cls: Type[FT],
|
||||||
|
source: str,
|
||||||
|
*,
|
||||||
|
method: Optional[Union[str, Callable[[str, str], Tuple[Optional[str], Optional[int]]]]] = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> FT:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
A factory method that creates a :class:`FFmpegOpusAudio` after probing
|
A factory method that creates a :class:`FFmpegOpusAudio` after probing
|
||||||
@@ -347,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)
|
||||||
@@ -382,10 +448,16 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
|
|
||||||
executable = kwargs.get('executable')
|
executable = kwargs.get('executable')
|
||||||
codec, bitrate = await cls.probe(source, method=method, executable=executable)
|
codec, bitrate = await cls.probe(source, method=method, executable=executable)
|
||||||
return cls(source, bitrate=bitrate, codec=codec, **kwargs)
|
return cls(source, bitrate=bitrate, codec=codec, **kwargs) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def probe(cls, source, *, method=None, executable=None):
|
async def probe(
|
||||||
|
cls,
|
||||||
|
source: str,
|
||||||
|
*,
|
||||||
|
method: Optional[Union[str, Callable[[str, str], Tuple[Optional[str], Optional[int]]]]] = None,
|
||||||
|
executable: Optional[str] = None,
|
||||||
|
) -> Tuple[Optional[str], Optional[int]]:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Probes the input source for bitrate and codec information.
|
Probes the input source for bitrate and codec information.
|
||||||
@@ -408,7 +480,7 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
Tuple[Optional[:class:`str`], Optional[:class:`int`]]
|
Optional[Tuple[Optional[:class:`str`], Optional[:class:`int`]]]
|
||||||
A 2-tuple with the codec and bitrate of the input source.
|
A 2-tuple with the codec and bitrate of the input source.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -434,26 +506,26 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
codec = bitrate = None
|
codec = bitrate = None
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
codec, bitrate = await loop.run_in_executor(None, lambda: probefunc(source, executable))
|
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
|
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))
|
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
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _probe_codec_native(source, executable='ffmpeg'):
|
def _probe_codec_native(source, executable: str = 'ffmpeg') -> Tuple[Optional[str], Optional[int]]:
|
||||||
exe = executable[:2] + 'probe' if executable in ('ffmpeg', 'avconv') else executable
|
exe = executable[:2] + 'probe' if executable in ('ffmpeg', 'avconv') else executable
|
||||||
args = [exe, '-v', 'quiet', '-print_format', 'json', '-show_streams', '-select_streams', 'a:0', source]
|
args = [exe, '-v', 'quiet', '-print_format', 'json', '-show_streams', '-select_streams', 'a:0', source]
|
||||||
output = subprocess.check_output(args, timeout=20)
|
output = subprocess.check_output(args, timeout=20)
|
||||||
@@ -465,12 +537,12 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
|
|
||||||
codec = streamdata.get('codec_name')
|
codec = streamdata.get('codec_name')
|
||||||
bitrate = int(streamdata.get('bit_rate', 0))
|
bitrate = int(streamdata.get('bit_rate', 0))
|
||||||
bitrate = max(round(bitrate/1000, 0), 512)
|
bitrate = max(round(bitrate/1000), 512)
|
||||||
|
|
||||||
return codec, bitrate
|
return codec, bitrate
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _probe_codec_fallback(source, executable='ffmpeg'):
|
def _probe_codec_fallback(source, executable: str = 'ffmpeg') -> Tuple[Optional[str], Optional[int]]:
|
||||||
args = [executable, '-hide_banner', '-i', source]
|
args = [executable, '-hide_banner', '-i', source]
|
||||||
proc = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
proc = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
out, _ = proc.communicate(timeout=20)
|
out, _ = proc.communicate(timeout=20)
|
||||||
@@ -487,13 +559,13 @@ class FFmpegOpusAudio(FFmpegAudio):
|
|||||||
|
|
||||||
return codec, bitrate
|
return codec, bitrate
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> bytes:
|
||||||
return next(self._packet_iter, b'')
|
return next(self._packet_iter, b'')
|
||||||
|
|
||||||
def is_opus(self):
|
def is_opus(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class PCMVolumeTransformer(AudioSource):
|
class PCMVolumeTransformer(AudioSource, Generic[AT]):
|
||||||
"""Transforms a previous :class:`AudioSource` to have volume controls.
|
"""Transforms a previous :class:`AudioSource` to have volume controls.
|
||||||
|
|
||||||
This does not work on audio sources that have :meth:`AudioSource.is_opus`
|
This does not work on audio sources that have :meth:`AudioSource.is_opus`
|
||||||
@@ -515,53 +587,53 @@ class PCMVolumeTransformer(AudioSource):
|
|||||||
The audio source is opus encoded.
|
The audio source is opus encoded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, original, volume=1.0):
|
def __init__(self, original: AT, volume: float = 1.0):
|
||||||
if not isinstance(original, AudioSource):
|
if not isinstance(original, AudioSource):
|
||||||
raise TypeError(f'expected AudioSource not {original.__class__.__name__}.')
|
raise TypeError(f'expected AudioSource not {original.__class__.__name__}.')
|
||||||
|
|
||||||
if original.is_opus():
|
if original.is_opus():
|
||||||
raise ClientException('AudioSource must not be Opus encoded.')
|
raise ClientException('AudioSource must not be Opus encoded.')
|
||||||
|
|
||||||
self.original = original
|
self.original: AT = original
|
||||||
self.volume = volume
|
self.volume = volume
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volume(self):
|
def volume(self) -> float:
|
||||||
"""Retrieves or sets the volume as a floating point percentage (e.g. ``1.0`` for 100%)."""
|
"""Retrieves or sets the volume as a floating point percentage (e.g. ``1.0`` for 100%)."""
|
||||||
return self._volume
|
return self._volume
|
||||||
|
|
||||||
@volume.setter
|
@volume.setter
|
||||||
def volume(self, value):
|
def volume(self, value: float) -> None:
|
||||||
self._volume = max(value, 0.0)
|
self._volume = max(value, 0.0)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self) -> None:
|
||||||
self.original.cleanup()
|
self.original.cleanup()
|
||||||
|
|
||||||
def read(self):
|
def read(self) -> bytes:
|
||||||
ret = self.original.read()
|
ret = self.original.read()
|
||||||
return audioop.mul(ret, 2, min(self._volume, 2.0))
|
return audioop.mul(ret, 2, min(self._volume, 2.0))
|
||||||
|
|
||||||
class AudioPlayer(threading.Thread):
|
class AudioPlayer(threading.Thread):
|
||||||
DELAY = OpusEncoder.FRAME_LENGTH / 1000.0
|
DELAY: float = OpusEncoder.FRAME_LENGTH / 1000.0
|
||||||
|
|
||||||
def __init__(self, source, client, *, after=None):
|
def __init__(self, source: AudioSource, client: VoiceClient, *, after=None):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon: bool = True
|
||||||
self.source = source
|
self.source: AudioSource = source
|
||||||
self.client = client
|
self.client: VoiceClient = client
|
||||||
self.after = after
|
self.after: Optional[Callable[[Optional[Exception]], Any]] = after
|
||||||
|
|
||||||
self._end = threading.Event()
|
self._end: threading.Event = threading.Event()
|
||||||
self._resumed = threading.Event()
|
self._resumed: threading.Event = threading.Event()
|
||||||
self._resumed.set() # we are not paused
|
self._resumed.set() # we are not paused
|
||||||
self._current_error = None
|
self._current_error: Optional[Exception] = None
|
||||||
self._connected = client._connected
|
self._connected: threading.Event = client._connected
|
||||||
self._lock = threading.Lock()
|
self._lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
if after is not None and not callable(after):
|
if after is not None and not callable(after):
|
||||||
raise TypeError('Expected a callable for the "after" parameter.')
|
raise TypeError('Expected a callable for the "after" parameter.')
|
||||||
|
|
||||||
def _do_run(self):
|
def _do_run(self) -> None:
|
||||||
self.loops = 0
|
self.loops = 0
|
||||||
self._start = time.perf_counter()
|
self._start = time.perf_counter()
|
||||||
|
|
||||||
@@ -596,7 +668,7 @@ class AudioPlayer(threading.Thread):
|
|||||||
delay = max(0, self.DELAY + (next_time - time.perf_counter()))
|
delay = max(0, self.DELAY + (next_time - time.perf_counter()))
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
try:
|
try:
|
||||||
self._do_run()
|
self._do_run()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -606,53 +678,53 @@ class AudioPlayer(threading.Thread):
|
|||||||
self.source.cleanup()
|
self.source.cleanup()
|
||||||
self._call_after()
|
self._call_after()
|
||||||
|
|
||||||
def _call_after(self):
|
def _call_after(self) -> None:
|
||||||
error = self._current_error
|
error = self._current_error
|
||||||
|
|
||||||
if self.after is not None:
|
if self.after is not None:
|
||||||
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__)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
self._end.set()
|
self._end.set()
|
||||||
self._resumed.set()
|
self._resumed.set()
|
||||||
self._speak(False)
|
self._speak(False)
|
||||||
|
|
||||||
def pause(self, *, update_speaking=True):
|
def pause(self, *, update_speaking: bool = True) -> None:
|
||||||
self._resumed.clear()
|
self._resumed.clear()
|
||||||
if update_speaking:
|
if update_speaking:
|
||||||
self._speak(False)
|
self._speak(False)
|
||||||
|
|
||||||
def resume(self, *, update_speaking=True):
|
def resume(self, *, update_speaking: bool = True) -> None:
|
||||||
self.loops = 0
|
self.loops = 0
|
||||||
self._start = time.perf_counter()
|
self._start = time.perf_counter()
|
||||||
self._resumed.set()
|
self._resumed.set()
|
||||||
if update_speaking:
|
if update_speaking:
|
||||||
self._speak(True)
|
self._speak(True)
|
||||||
|
|
||||||
def is_playing(self):
|
def is_playing(self) -> bool:
|
||||||
return self._resumed.is_set() and not self._end.is_set()
|
return self._resumed.is_set() and not self._end.is_set()
|
||||||
|
|
||||||
def is_paused(self):
|
def is_paused(self) -> bool:
|
||||||
return not self._end.is_set() and not self._resumed.is_set()
|
return not self._end.is_set() and not self._resumed.is_set()
|
||||||
|
|
||||||
def _set_source(self, source):
|
def _set_source(self, source: AudioSource) -> None:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.pause(update_speaking=False)
|
self.pause(update_speaking=False)
|
||||||
self.source = source
|
self.source = source
|
||||||
self.resume(update_speaking=False)
|
self.resume(update_speaking=False)
|
||||||
|
|
||||||
def _speak(self, speaking):
|
def _speak(self, speaking: bool) -> None:
|
||||||
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
0
discord/py.typed
Normal file
@@ -22,6 +22,28 @@ 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 TYPE_CHECKING, Optional, Set, List
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .types.raw_models import (
|
||||||
|
MessageDeleteEvent,
|
||||||
|
BulkMessageDeleteEvent,
|
||||||
|
ReactionActionEvent,
|
||||||
|
MessageUpdateEvent,
|
||||||
|
ReactionClearEvent,
|
||||||
|
ReactionClearEmojiEvent,
|
||||||
|
IntegrationDeleteEvent
|
||||||
|
)
|
||||||
|
from .message import Message
|
||||||
|
from .partial_emoji import PartialEmoji
|
||||||
|
from .member import Member
|
||||||
|
from .threads import Thread
|
||||||
|
|
||||||
|
|
||||||
|
from .enums import ChannelType, try_enum
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'RawMessageDeleteEvent',
|
'RawMessageDeleteEvent',
|
||||||
'RawBulkMessageDeleteEvent',
|
'RawBulkMessageDeleteEvent',
|
||||||
@@ -30,13 +52,16 @@ __all__ = (
|
|||||||
'RawReactionClearEvent',
|
'RawReactionClearEvent',
|
||||||
'RawReactionClearEmojiEvent',
|
'RawReactionClearEmojiEvent',
|
||||||
'RawIntegrationDeleteEvent',
|
'RawIntegrationDeleteEvent',
|
||||||
|
'RawThreadDeleteEvent',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class _RawReprMixin:
|
class _RawReprMixin:
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__)
|
value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__)
|
||||||
return f'<{self.__class__.__name__} {value}>'
|
return f'<{self.__class__.__name__} {value}>'
|
||||||
|
|
||||||
|
|
||||||
class RawMessageDeleteEvent(_RawReprMixin):
|
class RawMessageDeleteEvent(_RawReprMixin):
|
||||||
"""Represents the event payload for a :func:`on_raw_message_delete` event.
|
"""Represents the event payload for a :func:`on_raw_message_delete` event.
|
||||||
|
|
||||||
@@ -54,14 +79,15 @@ class RawMessageDeleteEvent(_RawReprMixin):
|
|||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'guild_id', 'cached_message')
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'cached_message')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data: MessageDeleteEvent) -> None:
|
||||||
self.message_id = int(data['id'])
|
self.message_id: int = int(data['id'])
|
||||||
self.channel_id = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
self.cached_message = None
|
self.cached_message: Optional[Message] = None
|
||||||
try:
|
try:
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: Optional[int] = int(data['guild_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.guild_id = None
|
self.guild_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class RawBulkMessageDeleteEvent(_RawReprMixin):
|
class RawBulkMessageDeleteEvent(_RawReprMixin):
|
||||||
"""Represents the event payload for a :func:`on_raw_bulk_message_delete` event.
|
"""Represents the event payload for a :func:`on_raw_bulk_message_delete` event.
|
||||||
@@ -80,15 +106,16 @@ class RawBulkMessageDeleteEvent(_RawReprMixin):
|
|||||||
|
|
||||||
__slots__ = ('message_ids', 'channel_id', 'guild_id', 'cached_messages')
|
__slots__ = ('message_ids', 'channel_id', 'guild_id', 'cached_messages')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data: BulkMessageDeleteEvent) -> None:
|
||||||
self.message_ids = {int(x) for x in data.get('ids', [])}
|
self.message_ids: Set[int] = {int(x) for x in data.get('ids', [])}
|
||||||
self.channel_id = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
self.cached_messages = []
|
self.cached_messages: List[Message] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: Optional[int] = int(data['guild_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.guild_id = None
|
self.guild_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class RawMessageUpdateEvent(_RawReprMixin):
|
class RawMessageUpdateEvent(_RawReprMixin):
|
||||||
"""Represents the payload for a :func:`on_raw_message_edit` event.
|
"""Represents the payload for a :func:`on_raw_message_edit` event.
|
||||||
@@ -115,16 +142,17 @@ class RawMessageUpdateEvent(_RawReprMixin):
|
|||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'guild_id', 'data', 'cached_message')
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'data', 'cached_message')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data: MessageUpdateEvent) -> None:
|
||||||
self.message_id = int(data['id'])
|
self.message_id: int = int(data['id'])
|
||||||
self.channel_id = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
self.data = data
|
self.data: MessageUpdateEvent = data
|
||||||
self.cached_message = None
|
self.cached_message: Optional[Message] = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: Optional[int] = int(data['guild_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.guild_id = None
|
self.guild_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class RawReactionActionEvent(_RawReprMixin):
|
class RawReactionActionEvent(_RawReprMixin):
|
||||||
"""Represents the payload for a :func:`on_raw_reaction_add` or
|
"""Represents the payload for a :func:`on_raw_reaction_add` or
|
||||||
@@ -158,18 +186,19 @@ class RawReactionActionEvent(_RawReprMixin):
|
|||||||
__slots__ = ('message_id', 'user_id', 'channel_id', 'guild_id', 'emoji',
|
__slots__ = ('message_id', 'user_id', 'channel_id', 'guild_id', 'emoji',
|
||||||
'event_type', 'member')
|
'event_type', 'member')
|
||||||
|
|
||||||
def __init__(self, data, emoji, event_type):
|
def __init__(self, data: ReactionActionEvent, emoji: PartialEmoji, event_type: str) -> None:
|
||||||
self.message_id = int(data['message_id'])
|
self.message_id: int = int(data['message_id'])
|
||||||
self.channel_id = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
self.user_id = int(data['user_id'])
|
self.user_id: int = int(data['user_id'])
|
||||||
self.emoji = emoji
|
self.emoji: PartialEmoji = emoji
|
||||||
self.event_type = event_type
|
self.event_type: str = event_type
|
||||||
self.member = None
|
self.member: Optional[Member] = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: Optional[int] = int(data['guild_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.guild_id = None
|
self.guild_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class RawReactionClearEvent(_RawReprMixin):
|
class RawReactionClearEvent(_RawReprMixin):
|
||||||
"""Represents the payload for a :func:`on_raw_reaction_clear` event.
|
"""Represents the payload for a :func:`on_raw_reaction_clear` event.
|
||||||
@@ -186,14 +215,15 @@ class RawReactionClearEvent(_RawReprMixin):
|
|||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'guild_id')
|
__slots__ = ('message_id', 'channel_id', 'guild_id')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data: ReactionClearEvent) -> None:
|
||||||
self.message_id = int(data['message_id'])
|
self.message_id: int = int(data['message_id'])
|
||||||
self.channel_id = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: Optional[int] = int(data['guild_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.guild_id = None
|
self.guild_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class RawReactionClearEmojiEvent(_RawReprMixin):
|
class RawReactionClearEmojiEvent(_RawReprMixin):
|
||||||
"""Represents the payload for a :func:`on_raw_reaction_clear_emoji` event.
|
"""Represents the payload for a :func:`on_raw_reaction_clear_emoji` event.
|
||||||
@@ -214,15 +244,16 @@ class RawReactionClearEmojiEvent(_RawReprMixin):
|
|||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'guild_id', 'emoji')
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'emoji')
|
||||||
|
|
||||||
def __init__(self, data, emoji):
|
def __init__(self, data: ReactionClearEmojiEvent, emoji: PartialEmoji) -> None:
|
||||||
self.emoji = emoji
|
self.emoji: PartialEmoji = emoji
|
||||||
self.message_id = int(data['message_id'])
|
self.message_id: int = int(data['message_id'])
|
||||||
self.channel_id = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: Optional[int] = int(data['guild_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.guild_id = None
|
self.guild_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class RawIntegrationDeleteEvent(_RawReprMixin):
|
class RawIntegrationDeleteEvent(_RawReprMixin):
|
||||||
"""Represents the payload for a :func:`on_raw_integration_delete` event.
|
"""Represents the payload for a :func:`on_raw_integration_delete` event.
|
||||||
@@ -241,11 +272,39 @@ class RawIntegrationDeleteEvent(_RawReprMixin):
|
|||||||
|
|
||||||
__slots__ = ('integration_id', 'application_id', 'guild_id')
|
__slots__ = ('integration_id', 'application_id', 'guild_id')
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data: IntegrationDeleteEvent) -> None:
|
||||||
self.integration_id = int(data['id'])
|
self.integration_id: int = int(data['id'])
|
||||||
self.guild_id = int(data['guild_id'])
|
self.guild_id: int = int(data['guild_id'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.application_id = int(data['application_id'])
|
self.application_id: Optional[int] = int(data['application_id'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.application_id = None
|
self.application_id: Optional[int] = None
|
||||||
|
|
||||||
|
class RawThreadDeleteEvent(_RawReprMixin):
|
||||||
|
"""Represents the payload for a :func:`on_raw_thread_delete` event.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
thread_id: :class:`int`
|
||||||
|
The ID of the thread that was deleted.
|
||||||
|
thread_type: :class:`discord.ChannelType`
|
||||||
|
The channel type of the deleted thread.
|
||||||
|
guild_id: :class:`int`
|
||||||
|
The ID of the guild the thread was deleted in.
|
||||||
|
parent_id: :class:`int`
|
||||||
|
The ID of the channel the thread was belonged to.
|
||||||
|
thread: Optional[:class:`discord.Thread`]
|
||||||
|
The thread, if it could be found in the internal cache.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('thread_id', 'thread_type', 'parent_id', 'guild_id', 'thread')
|
||||||
|
|
||||||
|
def __init__(self, data) -> None:
|
||||||
|
self.thread_id: int = int(data['id'])
|
||||||
|
self.thread_type: ChannelType = try_enum(ChannelType, data['type'])
|
||||||
|
self.guild_id: int = int(data['guild_id'])
|
||||||
|
self.parent_id: int = int(data['parent_id'])
|
||||||
|
self.thread: Optional[Thread] = None
|
||||||
|
|||||||
@@ -22,12 +22,22 @@ 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 Any, TYPE_CHECKING, Union, Optional
|
||||||
|
|
||||||
from .iterators import ReactionIterator
|
from .iterators import ReactionIterator
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Reaction',
|
'Reaction',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .types.message import Reaction as ReactionPayload
|
||||||
|
from .message import Message
|
||||||
|
from .partial_emoji import PartialEmoji
|
||||||
|
from .emoji import Emoji
|
||||||
|
from .abc import Snowflake
|
||||||
|
|
||||||
class Reaction:
|
class Reaction:
|
||||||
"""Represents a reaction to a message.
|
"""Represents a reaction to a message.
|
||||||
|
|
||||||
@@ -67,35 +77,35 @@ class Reaction:
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('message', 'count', 'emoji', 'me')
|
__slots__ = ('message', 'count', 'emoji', 'me')
|
||||||
|
|
||||||
def __init__(self, *, message, data, emoji=None):
|
def __init__(self, *, message: Message, data: ReactionPayload, emoji: Optional[Union[PartialEmoji, Emoji, str]] = None):
|
||||||
self.message = message
|
self.message: Message = message
|
||||||
self.emoji = emoji or message._state.get_reaction_emoji(data['emoji'])
|
self.emoji: Union[PartialEmoji, Emoji, str] = emoji or message._state.get_reaction_emoji(data['emoji'])
|
||||||
self.count = data.get('count', 1)
|
self.count: int = data.get('count', 1)
|
||||||
self.me = data.get('me')
|
self.me: bool = data.get('me')
|
||||||
|
|
||||||
@property
|
# TODO: typeguard
|
||||||
def custom_emoji(self):
|
def is_custom_emoji(self) -> bool:
|
||||||
""":class:`bool`: If this is a custom emoji."""
|
""":class:`bool`: If this is a custom emoji."""
|
||||||
return not isinstance(self.emoji, str)
|
return not isinstance(self.emoji, str)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
return isinstance(other, self.__class__) and other.emoji == self.emoji
|
return isinstance(other, self.__class__) and other.emoji == self.emoji
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other: Any) -> bool:
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
return other.emoji != self.emoji
|
return other.emoji != self.emoji
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash(self.emoji)
|
return hash(self.emoji)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return str(self.emoji)
|
return str(self.emoji)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'<Reaction emoji={self.emoji!r} me={self.me} count={self.count}>'
|
return f'<Reaction emoji={self.emoji!r} me={self.me} count={self.count}>'
|
||||||
|
|
||||||
async def remove(self, user):
|
async def remove(self, user: Snowflake) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Remove the reaction by the provided :class:`User` from the message.
|
Remove the reaction by the provided :class:`User` from the message.
|
||||||
@@ -123,7 +133,7 @@ class Reaction:
|
|||||||
|
|
||||||
await self.message.remove_reaction(self.emoji, user)
|
await self.message.remove_reaction(self.emoji, user)
|
||||||
|
|
||||||
async def clear(self):
|
async def clear(self) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Clears this reaction from the message.
|
Clears this reaction from the message.
|
||||||
@@ -145,7 +155,7 @@ class Reaction:
|
|||||||
"""
|
"""
|
||||||
await self.message.clear_reaction(self.emoji)
|
await self.message.clear_reaction(self.emoji)
|
||||||
|
|
||||||
def users(self, limit=None, after=None):
|
def users(self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None) -> ReactionIterator:
|
||||||
"""Returns an :class:`AsyncIterator` representing the users that have reacted to the message.
|
"""Returns an :class:`AsyncIterator` representing the users that have reacted to the message.
|
||||||
|
|
||||||
The ``after`` parameter must represent a member
|
The ``after`` parameter must represent a member
|
||||||
@@ -169,11 +179,11 @@ class Reaction:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
limit: :class:`int`
|
limit: Optional[:class:`int`]
|
||||||
The maximum number of results to return.
|
The maximum number of results to return.
|
||||||
If not provided, returns all the users who
|
If not provided, returns all the users who
|
||||||
reacted to the message.
|
reacted to the message.
|
||||||
after: :class:`abc.Snowflake`
|
after: Optional[:class:`abc.Snowflake`]
|
||||||
For pagination, reactions are sorted by member.
|
For pagination, reactions are sorted by member.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
@@ -190,7 +200,7 @@ class Reaction:
|
|||||||
if the member has left the guild.
|
if the member has left the guild.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.custom_emoji:
|
if not isinstance(self.emoji, str):
|
||||||
emoji = f'{self.emoji.name}:{self.emoji.id}'
|
emoji = f'{self.emoji.name}:{self.emoji.id}'
|
||||||
else:
|
else:
|
||||||
emoji = self.emoji
|
emoji = self.emoji
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Any, List, Optional, TypeVar, Union, overload, TYPE_CHECKING
|
from typing import Any, Dict, List, Optional, TypeVar, Union, overload, TYPE_CHECKING
|
||||||
|
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
@@ -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,28 +348,21 @@ 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)
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(
|
async def edit(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
reason: Optional[str] = ...,
|
name: str = MISSING,
|
||||||
name: str = ...,
|
permissions: Permissions = MISSING,
|
||||||
permissions: Permissions = ...,
|
colour: Union[Colour, int] = MISSING,
|
||||||
colour: Union[Colour, int] = ...,
|
color: Union[Colour, int] = MISSING,
|
||||||
hoist: bool = ...,
|
hoist: bool = MISSING,
|
||||||
mentionable: bool = ...,
|
mentionable: bool = MISSING,
|
||||||
position: int = ...,
|
position: int = MISSING,
|
||||||
) -> None:
|
reason: Optional[str] = MISSING,
|
||||||
...
|
) -> Optional[Role]:
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(self) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
async def edit(self, *, reason=None, **fields) -> None:
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the role.
|
Edits the role.
|
||||||
@@ -370,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`
|
||||||
@@ -397,31 +405,39 @@ 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:
|
||||||
position = fields.get('position')
|
|
||||||
if position is not None:
|
|
||||||
await self._move(position, reason=reason)
|
await self._move(position, reason=reason)
|
||||||
self.position = position
|
|
||||||
|
|
||||||
try:
|
payload: Dict[str, Any] = {}
|
||||||
colour = fields['colour']
|
if color is not MISSING:
|
||||||
except KeyError:
|
colour = color
|
||||||
colour = fields.get('color', self.colour)
|
|
||||||
|
|
||||||
|
if colour is not MISSING:
|
||||||
if isinstance(colour, int):
|
if isinstance(colour, int):
|
||||||
colour = Colour(value=colour)
|
payload['color'] = colour
|
||||||
|
else:
|
||||||
|
payload['color'] = colour.value
|
||||||
|
|
||||||
payload = {
|
if name is not MISSING:
|
||||||
'name': fields.get('name', self.name),
|
payload['name'] = name
|
||||||
'permissions': str(fields.get('permissions', self.permissions).value),
|
|
||||||
'color': colour.value,
|
if permissions is not MISSING:
|
||||||
'hoist': fields.get('hoist', self.hoist),
|
payload['permissions'] = permissions.value
|
||||||
'mentionable': fields.get('mentionable', self.mentionable),
|
|
||||||
}
|
if hoist is not MISSING:
|
||||||
|
payload['hoist'] = hoist
|
||||||
|
|
||||||
|
if mentionable is not MISSING:
|
||||||
|
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|
|
||||||
|
|||||||
183
discord/shard.py
183
discord/shard.py
@@ -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, afk=False, 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.
|
||||||
@@ -446,6 +481,9 @@ class AutoShardedClient(Client):
|
|||||||
game = discord.Game("with the API")
|
game = discord.Game("with the API")
|
||||||
await client.change_presence(status=discord.Status.idle, activity=game)
|
await client.change_presence(status=discord.Status.idle, activity=game)
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Removed the ``afk`` keyword-only parameter.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
activity: Optional[:class:`BaseActivity`]
|
activity: Optional[:class:`BaseActivity`]
|
||||||
@@ -453,10 +491,6 @@ class AutoShardedClient(Client):
|
|||||||
status: Optional[:class:`Status`]
|
status: Optional[:class:`Status`]
|
||||||
Indicates what status to change to. If ``None``, then
|
Indicates what status to change to. If ``None``, then
|
||||||
:attr:`Status.online` is used.
|
:attr:`Status.online` is used.
|
||||||
afk: :class:`bool`
|
|
||||||
Indicates if you are going AFK. This allows the discord
|
|
||||||
client to know how to handle push notifications better
|
|
||||||
for you in case you are actually idle and not lying.
|
|
||||||
shard_id: Optional[:class:`int`]
|
shard_id: Optional[:class:`int`]
|
||||||
The shard_id to change the presence to. If not specified
|
The shard_id to change the presence to. If not specified
|
||||||
or ``None``, then it will change the presence of every
|
or ``None``, then it will change the presence of every
|
||||||
@@ -469,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, afk=afk)
|
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, afk=afk)
|
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,)
|
||||||
@@ -494,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
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class StageInstance(Hashable):
|
|||||||
|
|
||||||
.. describe:: x == y
|
.. describe:: x == y
|
||||||
|
|
||||||
Checks if two stagea instances are equal.
|
Checks if two stage instances are equal.
|
||||||
|
|
||||||
.. describe:: x != y
|
.. describe:: x != y
|
||||||
|
|
||||||
@@ -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`
|
||||||
@@ -74,7 +78,7 @@ class StageInstance(Hashable):
|
|||||||
privacy_level: :class:`StagePrivacyLevel`
|
privacy_level: :class:`StagePrivacyLevel`
|
||||||
The privacy level of the stage instance.
|
The privacy level of the stage instance.
|
||||||
discoverable_disabled: :class:`bool`
|
discoverable_disabled: :class:`bool`
|
||||||
Whether the stage instance is discoverable.
|
Whether discoverability for the stage instance is disabled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
@@ -97,21 +101,22 @@ class StageInstance(Hashable):
|
|||||||
self.id: int = int(data['id'])
|
self.id: int = int(data['id'])
|
||||||
self.channel_id: int = int(data['channel_id'])
|
self.channel_id: int = int(data['channel_id'])
|
||||||
self.topic: str = data['topic']
|
self.topic: str = data['topic']
|
||||||
self.privacy_level = try_enum(StagePrivacyLevel, data['privacy_level'])
|
self.privacy_level: StagePrivacyLevel = try_enum(StagePrivacyLevel, data['privacy_level'])
|
||||||
self.discoverable_disabled = data['discoverable_disabled']
|
self.discoverable_disabled: bool = data.get('discoverable_disabled', False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<StageInstance id={self.id} guild={self.guild!r} channel_id={self.channel_id} topic={self.topic!r}>'
|
return f'<StageInstance id={self.id} guild={self.guild!r} channel_id={self.channel_id} topic={self.topic!r}>'
|
||||||
|
|
||||||
@cached_slot_property('_cs_channel')
|
@cached_slot_property('_cs_channel')
|
||||||
def channel(self) -> Optional[StageChannel]:
|
def channel(self) -> Optional[StageChannel]:
|
||||||
"""Optional[:class:`StageChannel`: The guild 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
|
||||||
|
|
||||||
async def edit(self, *, topic: str = MISSING, privacy_level: StagePrivacyLevel = MISSING) -> None:
|
async def edit(self, *, topic: str = MISSING, privacy_level: StagePrivacyLevel = MISSING, reason: Optional[str] = None) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edits the stage instance.
|
Edits the stage instance.
|
||||||
@@ -125,6 +130,8 @@ class StageInstance(Hashable):
|
|||||||
The stage instance's new topic.
|
The stage instance's new topic.
|
||||||
privacy_level: :class:`StagePrivacyLevel`
|
privacy_level: :class:`StagePrivacyLevel`
|
||||||
The stage instance's new privacy level.
|
The stage instance's new privacy level.
|
||||||
|
reason: :class:`str`
|
||||||
|
The reason the stage instance was edited. Shows up on the audit log.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
@@ -148,9 +155,9 @@ class StageInstance(Hashable):
|
|||||||
payload['privacy_level'] = privacy_level.value
|
payload['privacy_level'] = privacy_level.value
|
||||||
|
|
||||||
if payload:
|
if payload:
|
||||||
await self._state.http.edit_stage_instance(self.channel_id, **payload)
|
await self._state.http.edit_stage_instance(self.channel_id, **payload, reason=reason)
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self, *, reason: Optional[str] = None) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Deletes the stage instance.
|
Deletes the stage instance.
|
||||||
@@ -158,6 +165,11 @@ class StageInstance(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.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
reason: :class:`str`
|
||||||
|
The reason the stage instance was deleted. Shows up on the audit log.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
Forbidden
|
Forbidden
|
||||||
@@ -165,4 +177,4 @@ class StageInstance(Hashable):
|
|||||||
HTTPException
|
HTTPException
|
||||||
Deleting the stage instance failed.
|
Deleting the stage instance failed.
|
||||||
"""
|
"""
|
||||||
await self._state.http.delete_stage_instance(self.channel_id)
|
await self._state.http.delete_stage_instance(self.channel_id, reason=reason)
|
||||||
|
|||||||
704
discord/state.py
704
discord/state.py
File diff suppressed because it is too large
Load Diff
@@ -23,24 +23,224 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING, List, Optional
|
from typing import Literal, TYPE_CHECKING, List, Optional, Tuple, Type, Union
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
from .asset import Asset
|
from .asset import Asset, AssetMixin
|
||||||
from .utils import snowflake_time
|
from .utils import cached_slot_property, find, snowflake_time, get, MISSING
|
||||||
from .enums import StickerType, try_enum
|
from .errors import InvalidData
|
||||||
|
from .enums import StickerType, StickerFormatType, try_enum
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'StickerPack',
|
||||||
|
'StickerItem',
|
||||||
'Sticker',
|
'Sticker',
|
||||||
|
'StandardSticker',
|
||||||
|
'GuildSticker',
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import datetime
|
import datetime
|
||||||
from .state import ConnectionState
|
from .state import ConnectionState
|
||||||
from .types.message import Sticker as StickerPayload
|
from .user import User
|
||||||
|
from .guild import Guild
|
||||||
|
from .types.sticker import (
|
||||||
|
StickerPack as StickerPackPayload,
|
||||||
|
StickerItem as StickerItemPayload,
|
||||||
|
Sticker as StickerPayload,
|
||||||
|
StandardSticker as StandardStickerPayload,
|
||||||
|
GuildSticker as GuildStickerPayload,
|
||||||
|
ListPremiumStickerPacks as ListPremiumStickerPacksPayload,
|
||||||
|
EditGuildSticker,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Sticker(Hashable):
|
class StickerPack(Hashable):
|
||||||
|
"""Represents a sticker pack.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Checks if the sticker pack is equal to another sticker pack.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if the sticker pack is not equal to another sticker pack.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the sticker pack.
|
||||||
|
description: :class:`str`
|
||||||
|
The description of the sticker pack.
|
||||||
|
id: :class:`int`
|
||||||
|
The id of the sticker pack.
|
||||||
|
stickers: List[:class:`StandardSticker`]
|
||||||
|
The stickers of this sticker pack.
|
||||||
|
sku_id: :class:`int`
|
||||||
|
The SKU ID of the sticker pack.
|
||||||
|
cover_sticker_id: :class:`int`
|
||||||
|
The ID of the sticker used for the cover of the sticker pack.
|
||||||
|
cover_sticker: :class:`StandardSticker`
|
||||||
|
The sticker used for the cover of the sticker pack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
'_state',
|
||||||
|
'id',
|
||||||
|
'stickers',
|
||||||
|
'name',
|
||||||
|
'sku_id',
|
||||||
|
'cover_sticker_id',
|
||||||
|
'cover_sticker',
|
||||||
|
'description',
|
||||||
|
'_banner',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *, state: ConnectionState, data: StickerPackPayload) -> None:
|
||||||
|
self._state: ConnectionState = state
|
||||||
|
self._from_data(data)
|
||||||
|
|
||||||
|
def _from_data(self, data: StickerPackPayload) -> None:
|
||||||
|
self.id: int = int(data['id'])
|
||||||
|
stickers = data['stickers']
|
||||||
|
self.stickers: List[StandardSticker] = [StandardSticker(state=self._state, data=sticker) for sticker in stickers]
|
||||||
|
self.name: str = data['name']
|
||||||
|
self.sku_id: int = int(data['sku_id'])
|
||||||
|
self.cover_sticker_id: int = int(data['cover_sticker_id'])
|
||||||
|
self.cover_sticker: StandardSticker = get(self.stickers, id=self.cover_sticker_id) # type: ignore
|
||||||
|
self.description: str = data['description']
|
||||||
|
self._banner: int = int(data['banner_asset_id'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def banner(self) -> Asset:
|
||||||
|
""":class:`Asset`: The banner asset of the sticker pack."""
|
||||||
|
return Asset._from_sticker_banner(self._state, self._banner)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<StickerPack id={self.id} name={self.name!r} description={self.description!r}>'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class _StickerTag(Hashable, AssetMixin):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
id: int
|
||||||
|
format: StickerFormatType
|
||||||
|
|
||||||
|
async def read(self) -> bytes:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves the content of this sticker as a :class:`bytes` object.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Stickers that use the :attr:`StickerFormatType.lottie` format cannot be read.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
HTTPException
|
||||||
|
Downloading the asset failed.
|
||||||
|
NotFound
|
||||||
|
The asset was deleted.
|
||||||
|
TypeError
|
||||||
|
The sticker is a lottie type.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`bytes`
|
||||||
|
The content of the asset.
|
||||||
|
"""
|
||||||
|
if self.format is StickerFormatType.lottie:
|
||||||
|
raise TypeError('Cannot read stickers of format "lottie".')
|
||||||
|
return await super().read()
|
||||||
|
|
||||||
|
|
||||||
|
class StickerItem(_StickerTag):
|
||||||
|
"""Represents a sticker item.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
Returns the name of the sticker item.
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if the sticker item is equal to another sticker item.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if the sticker item is not equal to another sticker item.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The sticker's name.
|
||||||
|
id: :class:`int`
|
||||||
|
The id of the sticker.
|
||||||
|
format: :class:`StickerFormatType`
|
||||||
|
The format for the sticker's image.
|
||||||
|
url: :class:`str`
|
||||||
|
The URL for the sticker's image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_state', 'name', 'id', 'format', 'url')
|
||||||
|
|
||||||
|
def __init__(self, *, state: ConnectionState, data: StickerItemPayload):
|
||||||
|
self._state: ConnectionState = state
|
||||||
|
self.name: str = data['name']
|
||||||
|
self.id: int = int(data['id'])
|
||||||
|
self.format: StickerFormatType = try_enum(StickerFormatType, data['format_type'])
|
||||||
|
self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<StickerItem id={self.id} name={self.name!r} format={self.format}>'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
async def fetch(self) -> Union[Sticker, StandardSticker, GuildSticker]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Attempts to retrieve the full sticker data of the sticker item.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
HTTPException
|
||||||
|
Retrieving the sticker failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Union[:class:`StandardSticker`, :class:`GuildSticker`]
|
||||||
|
The retrieved sticker.
|
||||||
|
"""
|
||||||
|
data: StickerPayload = await self._state.http.get_sticker(self.id)
|
||||||
|
cls, _ = _sticker_factory(data['type']) # type: ignore
|
||||||
|
return cls(state=self._state, data=data)
|
||||||
|
|
||||||
|
|
||||||
|
class Sticker(_StickerTag):
|
||||||
"""Represents a sticker.
|
"""Represents a sticker.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
@@ -69,30 +269,27 @@ class Sticker(Hashable):
|
|||||||
The description of the sticker.
|
The description of the sticker.
|
||||||
pack_id: :class:`int`
|
pack_id: :class:`int`
|
||||||
The id of the sticker's pack.
|
The id of the sticker's pack.
|
||||||
format: :class:`StickerType`
|
format: :class:`StickerFormatType`
|
||||||
The format for the sticker's image.
|
The format for the sticker's image.
|
||||||
tags: List[:class:`str`]
|
url: :class:`str`
|
||||||
A list of tags for the sticker.
|
The URL for the sticker's image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', '_image', 'tags')
|
__slots__ = ('_state', 'id', 'name', 'description', 'format', 'url')
|
||||||
|
|
||||||
def __init__(self, *, state: ConnectionState, data: StickerPayload):
|
def __init__(self, *, state: ConnectionState, data: StickerPayload) -> None:
|
||||||
self._state: ConnectionState = state
|
self._state: ConnectionState = state
|
||||||
|
self._from_data(data)
|
||||||
|
|
||||||
|
def _from_data(self, data: StickerPayload) -> None:
|
||||||
self.id: int = int(data['id'])
|
self.id: int = int(data['id'])
|
||||||
self.name: str = data['name']
|
self.name: str = data['name']
|
||||||
self.description: str = data['description']
|
self.description: str = data['description']
|
||||||
self.pack_id: int = int(data['pack_id'])
|
self.format: StickerFormatType = try_enum(StickerFormatType, data['format_type'])
|
||||||
self.format: StickerType = try_enum(StickerType, data['format_type'])
|
self.url: str = f'{Asset.BASE}/stickers/{self.id}.{self.format.file_extension}'
|
||||||
self._image: str = data['asset']
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.tags: List[str] = [tag.strip() for tag in data['tags'].split(',')]
|
|
||||||
except KeyError:
|
|
||||||
self.tags = []
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<{self.__class__.__name__} id={self.id} name={self.name!r}>'
|
return f'<Sticker id={self.id} name={self.name!r}>'
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
@@ -102,19 +299,233 @@ class Sticker(Hashable):
|
|||||||
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC."""
|
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC."""
|
||||||
return snowflake_time(self.id)
|
return snowflake_time(self.id)
|
||||||
|
|
||||||
@property
|
|
||||||
def image(self) -> Optional[Asset]:
|
|
||||||
"""Returns an :class:`Asset` for the sticker's image.
|
|
||||||
|
|
||||||
.. note::
|
class StandardSticker(Sticker):
|
||||||
This will return ``None`` if the format is ``StickerType.lottie``.
|
"""Represents a sticker that is found in a standard sticker pack.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
Returns the name of the sticker.
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if the sticker is equal to another sticker.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if the sticker is not equal to another sticker.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
name: :class:`str`
|
||||||
|
The sticker's name.
|
||||||
|
id: :class:`int`
|
||||||
|
The id of the sticker.
|
||||||
|
description: :class:`str`
|
||||||
|
The description of the sticker.
|
||||||
|
pack_id: :class:`int`
|
||||||
|
The id of the sticker's pack.
|
||||||
|
format: :class:`StickerFormatType`
|
||||||
|
The format for the sticker's image.
|
||||||
|
tags: List[:class:`str`]
|
||||||
|
A list of tags for the sticker.
|
||||||
|
sort_value: :class:`int`
|
||||||
|
The sticker's sort order within its pack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('sort_value', 'pack_id', 'type', 'tags')
|
||||||
|
|
||||||
|
def _from_data(self, data: StandardStickerPayload) -> None:
|
||||||
|
super()._from_data(data)
|
||||||
|
self.sort_value: int = data['sort_value']
|
||||||
|
self.pack_id: int = int(data['pack_id'])
|
||||||
|
self.type: StickerType = StickerType.standard
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.tags: List[str] = [tag.strip() for tag in data['tags'].split(',')]
|
||||||
|
except KeyError:
|
||||||
|
self.tags = []
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<StandardSticker id={self.id} name={self.name!r} pack_id={self.pack_id}>'
|
||||||
|
|
||||||
|
async def pack(self) -> StickerPack:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves the sticker pack that this sticker belongs to.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
InvalidData
|
||||||
|
The corresponding sticker pack was not found.
|
||||||
|
HTTPException
|
||||||
|
Retrieving the sticker pack failed.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
--------
|
||||||
Optional[:class:`Asset`]
|
:class:`StickerPack`
|
||||||
The resulting CDN asset.
|
The retrieved sticker pack.
|
||||||
"""
|
"""
|
||||||
if self.format is StickerType.lottie:
|
data: ListPremiumStickerPacksPayload = await self._state.http.list_premium_sticker_packs()
|
||||||
return None
|
packs = data['sticker_packs']
|
||||||
|
pack = find(lambda d: int(d['id']) == self.pack_id, packs)
|
||||||
|
|
||||||
return Asset._from_sticker(self._state, self.id, self._image)
|
if pack:
|
||||||
|
return StickerPack(state=self._state, data=pack)
|
||||||
|
raise InvalidData(f'Could not find corresponding sticker pack for {self!r}')
|
||||||
|
|
||||||
|
|
||||||
|
class GuildSticker(Sticker):
|
||||||
|
"""Represents a sticker that belongs to a guild.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
Returns the name of the sticker.
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if the sticker is equal to another sticker.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if the sticker is not equal to another sticker.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
name: :class:`str`
|
||||||
|
The sticker's name.
|
||||||
|
id: :class:`int`
|
||||||
|
The id of the sticker.
|
||||||
|
description: :class:`str`
|
||||||
|
The description of the sticker.
|
||||||
|
format: :class:`StickerFormatType`
|
||||||
|
The format for the sticker's image.
|
||||||
|
available: :class:`bool`
|
||||||
|
Whether this sticker is available for use.
|
||||||
|
guild_id: :class:`int`
|
||||||
|
The ID of the guild that this sticker is from.
|
||||||
|
user: Optional[:class:`User`]
|
||||||
|
The user that created this sticker. This can only be retrieved using :meth:`Guild.fetch_sticker` and
|
||||||
|
having the :attr:`~Permissions.manage_emojis_and_stickers` permission.
|
||||||
|
emoji: :class:`str`
|
||||||
|
The name of a unicode emoji that represents this sticker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('available', 'guild_id', 'user', 'emoji', 'type', '_cs_guild')
|
||||||
|
|
||||||
|
def _from_data(self, data: GuildStickerPayload) -> None:
|
||||||
|
super()._from_data(data)
|
||||||
|
self.available: bool = data['available']
|
||||||
|
self.guild_id: int = int(data['guild_id'])
|
||||||
|
user = data.get('user')
|
||||||
|
self.user: Optional[User] = self._state.store_user(user) if user else None
|
||||||
|
self.emoji: str = data['tags']
|
||||||
|
self.type: StickerType = StickerType.guild
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<GuildSticker name={self.name!r} id={self.id} guild_id={self.guild_id} user={self.user!r}>'
|
||||||
|
|
||||||
|
@cached_slot_property('_cs_guild')
|
||||||
|
def guild(self) -> Optional[Guild]:
|
||||||
|
"""Optional[:class:`Guild`]: The guild that this sticker is from.
|
||||||
|
Could be ``None`` if the bot is not in the guild.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
return self._state._get_guild(self.guild_id)
|
||||||
|
|
||||||
|
async def edit(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str = MISSING,
|
||||||
|
description: str = MISSING,
|
||||||
|
emoji: str = MISSING,
|
||||||
|
reason: Optional[str] = None,
|
||||||
|
) -> GuildSticker:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits a :class:`GuildSticker` for the guild.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
name: :class:`str`
|
||||||
|
The sticker's new name. Must be at least 2 characters.
|
||||||
|
description: Optional[:class:`str`]
|
||||||
|
The sticker's new description. Can be ``None``.
|
||||||
|
emoji: :class:`str`
|
||||||
|
The name of a unicode emoji that represents the sticker's expression.
|
||||||
|
reason: :class:`str`
|
||||||
|
The reason for editing this sticker. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You are not allowed to edit stickers.
|
||||||
|
HTTPException
|
||||||
|
An error occurred editing the sticker.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`GuildSticker`
|
||||||
|
The newly modified sticker.
|
||||||
|
"""
|
||||||
|
payload: EditGuildSticker = {}
|
||||||
|
|
||||||
|
if name is not MISSING:
|
||||||
|
payload['name'] = name
|
||||||
|
|
||||||
|
if description is not MISSING:
|
||||||
|
payload['description'] = description
|
||||||
|
|
||||||
|
if emoji is not MISSING:
|
||||||
|
try:
|
||||||
|
emoji = unicodedata.name(emoji)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
emoji = emoji.replace(' ', '_')
|
||||||
|
|
||||||
|
payload['tags'] = emoji
|
||||||
|
|
||||||
|
data: GuildStickerPayload = await self._state.http.modify_guild_sticker(self.guild_id, self.id, payload, reason)
|
||||||
|
return GuildSticker(state=self._state, data=data)
|
||||||
|
|
||||||
|
async def delete(self, *, reason: Optional[str] = None) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the custom :class:`Sticker` from the guild.
|
||||||
|
|
||||||
|
You must have :attr:`~Permissions.manage_emojis_and_stickers` permission to
|
||||||
|
do this.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
|
The reason for deleting this sticker. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You are not allowed to delete stickers.
|
||||||
|
HTTPException
|
||||||
|
An error occurred deleting the sticker.
|
||||||
|
"""
|
||||||
|
await self._state.http.delete_guild_sticker(self.guild_id, self.id, reason)
|
||||||
|
|
||||||
|
|
||||||
|
def _sticker_factory(sticker_type: Literal[1, 2]) -> Tuple[Type[Union[StandardSticker, GuildSticker, Sticker]], StickerType]:
|
||||||
|
value = try_enum(StickerType, sticker_type)
|
||||||
|
if value == StickerType.standard:
|
||||||
|
return StandardSticker, value
|
||||||
|
elif value == StickerType.guild:
|
||||||
|
return GuildSticker, value
|
||||||
|
else:
|
||||||
|
return Sticker, value
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class TeamMember(BaseUser):
|
|||||||
The membership state of the member (e.g. invited or accepted)
|
The membership state of the member (e.g. invited or accepted)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = BaseUser.__slots__ + ('team', 'membership_state', 'permissions')
|
__slots__ = ('team', 'membership_state', 'permissions')
|
||||||
|
|
||||||
def __init__(self, team: Team, state: ConnectionState, data: TeamMemberPayload):
|
def __init__(self, team: Team, state: ConnectionState, data: TeamMemberPayload):
|
||||||
self.team: Team = team
|
self.team: Team = team
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Optional, TYPE_CHECKING, overload
|
from typing import Any, Optional, TYPE_CHECKING
|
||||||
from .utils import parse_time, _get_as_snowflake, _bytes_to_base64_data
|
from .utils import parse_time, _get_as_snowflake, _bytes_to_base64_data, MISSING
|
||||||
from .enums import VoiceRegion
|
from .enums import VoiceRegion
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
|
|
||||||
@@ -34,7 +34,10 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
import datetime
|
||||||
from .types.template import Template as TemplatePayload
|
from .types.template import Template as TemplatePayload
|
||||||
|
from .state import ConnectionState
|
||||||
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
class _FriendlyHttpAttributeErrorHelper:
|
class _FriendlyHttpAttributeErrorHelper:
|
||||||
@@ -74,7 +77,10 @@ class _PartialTemplateState:
|
|||||||
def _get_message(self, id):
|
def _get_message(self, id):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def query_members(self, **kwargs):
|
def _get_guild(self, id):
|
||||||
|
return self.__state._get_guild(id)
|
||||||
|
|
||||||
|
async def query_members(self, **kwargs: Any):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
@@ -124,33 +130,35 @@ class Template:
|
|||||||
'_state',
|
'_state',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *, state, data: TemplatePayload):
|
def __init__(self, *, state: ConnectionState, data: TemplatePayload) -> None:
|
||||||
self._state = state
|
self._state = state
|
||||||
self._store(data)
|
self._store(data)
|
||||||
|
|
||||||
def _store(self, data: TemplatePayload):
|
def _store(self, data: TemplatePayload) -> None:
|
||||||
self.code = data['code']
|
self.code: str = data['code']
|
||||||
self.uses = data['usage_count']
|
self.uses: int = data['usage_count']
|
||||||
self.name = data['name']
|
self.name: str = data['name']
|
||||||
self.description = data['description']
|
self.description: Optional[str] = data['description']
|
||||||
creator_data = data.get('creator')
|
creator_data = data.get('creator')
|
||||||
self.creator = None if creator_data is None else self._state.store_user(creator_data)
|
self.creator: Optional[User] = None if creator_data is None else self._state.create_user(creator_data)
|
||||||
|
|
||||||
self.created_at = parse_time(data.get('created_at'))
|
self.created_at: Optional[datetime.datetime] = parse_time(data.get('created_at'))
|
||||||
self.updated_at = parse_time(data.get('updated_at'))
|
self.updated_at: Optional[datetime.datetime] = parse_time(data.get('updated_at'))
|
||||||
|
|
||||||
id = _get_as_snowflake(data, 'source_guild_id')
|
guild_id = int(data['source_guild_id'])
|
||||||
|
guild: Optional[Guild] = self._state._get_guild(guild_id)
|
||||||
|
|
||||||
guild = self._state._get_guild(id)
|
self.source_guild: Guild
|
||||||
|
if guild is None:
|
||||||
if guild is None and id:
|
|
||||||
source_serialised = data['serialized_source_guild']
|
source_serialised = data['serialized_source_guild']
|
||||||
source_serialised['id'] = id
|
source_serialised['id'] = guild_id
|
||||||
state = _PartialTemplateState(state=self._state)
|
state = _PartialTemplateState(state=self._state)
|
||||||
guild = Guild(data=source_serialised, state=state)
|
# Guild expects a ConnectionState, we're passing a _PartialTemplateState
|
||||||
|
self.source_guild = Guild(data=source_serialised, state=state) # type: ignore
|
||||||
|
else:
|
||||||
self.source_guild = guild
|
self.source_guild = guild
|
||||||
self.is_dirty = data.get('is_dirty', None)
|
|
||||||
|
self.is_dirty: Optional[bool] = data.get('is_dirty', None)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@@ -158,7 +166,7 @@ class Template:
|
|||||||
f' creator={self.creator!r} source_guild={self.source_guild!r} is_dirty={self.is_dirty}>'
|
f' creator={self.creator!r} source_guild={self.source_guild!r} is_dirty={self.is_dirty}>'
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create_guild(self, name: str, region: Optional[VoiceRegion] = None, icon: Any = None):
|
async def create_guild(self, name: str, region: Optional[VoiceRegion] = None, icon: Any = None) -> Guild:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a :class:`.Guild` using the template.
|
Creates a :class:`.Guild` using the template.
|
||||||
@@ -198,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.
|
||||||
@@ -208,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
|
||||||
@@ -216,25 +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)
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(
|
async def edit(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: Optional[str] = ...,
|
name: str = MISSING,
|
||||||
description: Optional[str] = ...,
|
description: Optional[str] = MISSING,
|
||||||
) -> None:
|
) -> Template:
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
|
||||||
async def edit(self) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
async def edit(self, **kwargs) -> None:
|
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Edit the template metadata.
|
Edit the template metadata.
|
||||||
@@ -244,12 +252,15 @@ 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: Optional[:class:`str`]
|
name: :class:`str`
|
||||||
The template's new name.
|
The template's new name.
|
||||||
description: Optional[:class:`str`]
|
description: Optional[:class:`str`]
|
||||||
The template's description.
|
The template's new description.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
@@ -259,9 +270,21 @@ 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.edit_template(self.source_guild.id, self.code, kwargs)
|
payload = {}
|
||||||
self._store(data)
|
|
||||||
|
if name is not MISSING:
|
||||||
|
payload['name'] = name
|
||||||
|
if description is not MISSING:
|
||||||
|
payload['description'] = description
|
||||||
|
|
||||||
|
data = await self._state.http.edit_template(self.source_guild.id, self.code, payload)
|
||||||
|
return Template(state=self._state, data=data)
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Callable, Dict, Iterable, List, Optional, Union, TYPE_CHECKING
|
from typing import Callable, Dict, Iterable, List, Optional, Union, TYPE_CHECKING
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -45,10 +46,11 @@ if TYPE_CHECKING:
|
|||||||
ThreadMetadata,
|
ThreadMetadata,
|
||||||
ThreadArchiveDuration,
|
ThreadArchiveDuration,
|
||||||
)
|
)
|
||||||
|
from .types.snowflake import SnowflakeList
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .channel import TextChannel
|
from .channel import TextChannel, CategoryChannel
|
||||||
from .member import Member
|
from .member import Member
|
||||||
from .message import Message
|
from .message import Message, PartialMessage
|
||||||
from .abc import Snowflake, SnowflakeTime
|
from .abc import Snowflake, SnowflakeTime
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
@@ -72,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.
|
||||||
@@ -109,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`
|
||||||
@@ -134,13 +143,14 @@ 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',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *, guild: Guild, data: ThreadPayload):
|
def __init__(self, *, guild: Guild, state: ConnectionState, data: ThreadPayload):
|
||||||
self._state: ConnectionState = guild._state
|
self._state: ConnectionState = state
|
||||||
self.guild = guild
|
self.guild = guild
|
||||||
self._members: Dict[int, ThreadMember] = {}
|
self._members: Dict[int, ThreadMember] = {}
|
||||||
self._from_data(data)
|
self._from_data(data)
|
||||||
@@ -165,6 +175,8 @@ class Thread(Messageable, Hashable):
|
|||||||
self._type = try_enum(ChannelType, data['type'])
|
self._type = try_enum(ChannelType, data['type'])
|
||||||
self.last_message_id = _get_as_snowflake(data, 'last_message_id')
|
self.last_message_id = _get_as_snowflake(data, 'last_message_id')
|
||||||
self.slowmode_delay = data.get('rate_limit_per_user', 0)
|
self.slowmode_delay = data.get('rate_limit_per_user', 0)
|
||||||
|
self.message_count = data['message_count']
|
||||||
|
self.member_count = data['member_count']
|
||||||
self._unroll_metadata(data['thread_metadata'])
|
self._unroll_metadata(data['thread_metadata'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -180,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:
|
||||||
@@ -187,21 +200,43 @@ class Thread(Messageable, Hashable):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self.slowmode_delay = data.get('rate_limit_per_user', 0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._unroll_metadata(data['thread_metadata'])
|
self._unroll_metadata(data['thread_metadata'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self) -> ChannelType:
|
||||||
|
""":class:`ChannelType`: The channel's Discord type."""
|
||||||
|
return self._type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self) -> Optional[TextChannel]:
|
def parent(self) -> Optional[TextChannel]:
|
||||||
"""Optional[:class:`TextChannel`]: The parent channel this thread belongs to."""
|
"""Optional[:class:`TextChannel`]: The parent channel this thread belongs to."""
|
||||||
return self.guild.get_channel(self.parent_id)
|
return self.guild.get_channel(self.parent_id) # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def owner(self) -> Optional[Member]:
|
def owner(self) -> Optional[Member]:
|
||||||
"""Optional[:class:`Member`]: The member this thread belongs to."""
|
"""Optional[:class:`Member`]: The member this thread belongs to."""
|
||||||
return self.guild.get_member(self.owner_id)
|
return self.guild.get_member(self.owner_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mention(self) -> str:
|
||||||
|
""":class:`str`: The string that allows you to mention the thread."""
|
||||||
|
return f'<#{self.id}>'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def members(self) -> List[ThreadMember]:
|
||||||
|
"""List[:class:`ThreadMember`]: A list of thread members in this thread.
|
||||||
|
|
||||||
|
This requires :attr:`Intents.members` to be properly filled. Most of the time however,
|
||||||
|
this data is not provided by the gateway and a call to :meth:`fetch_members` is
|
||||||
|
needed.
|
||||||
|
"""
|
||||||
|
return list(self._members.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_message(self) -> Optional[Message]:
|
def last_message(self) -> Optional[Message]:
|
||||||
"""Fetches the last message from this channel in cache.
|
"""Fetches the last message from this channel in cache.
|
||||||
@@ -223,6 +258,26 @@ class Thread(Messageable, Hashable):
|
|||||||
"""
|
"""
|
||||||
return self._state._get_message(self.last_message_id) if self.last_message_id else None
|
return self._state._get_message(self.last_message_id) if self.last_message_id else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def category(self) -> Optional[CategoryChannel]:
|
||||||
|
"""The category channel the parent channel belongs to, if applicable.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
ClientException
|
||||||
|
The parent channel was not cached and returned ``None``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[:class:`CategoryChannel`]
|
||||||
|
The parent channel's category.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parent = self.parent
|
||||||
|
if parent is None:
|
||||||
|
raise ClientException('Parent channel not found')
|
||||||
|
return parent.category
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def category_id(self) -> Optional[int]:
|
def category_id(self) -> Optional[int]:
|
||||||
"""The category channel ID the parent channel belongs to, if applicable.
|
"""The category channel ID the parent channel belongs to, if applicable.
|
||||||
@@ -259,6 +314,15 @@ class Thread(Messageable, Hashable):
|
|||||||
"""
|
"""
|
||||||
return self._type is ChannelType.news_thread
|
return self._type is ChannelType.news_thread
|
||||||
|
|
||||||
|
def is_nsfw(self) -> bool:
|
||||||
|
""":class:`bool`: Whether the thread is NSFW or not.
|
||||||
|
|
||||||
|
An NSFW thread is a thread that has a parent that is an NSFW channel,
|
||||||
|
i.e. :meth:`.TextChannel.is_nsfw` is ``True``.
|
||||||
|
"""
|
||||||
|
parent = self.parent
|
||||||
|
return parent is not None and parent.is_nsfw()
|
||||||
|
|
||||||
def permissions_for(self, obj: Union[Member, Role], /) -> Permissions:
|
def permissions_for(self, obj: Union[Member, Role], /) -> Permissions:
|
||||||
"""Handles permission resolution for the :class:`~discord.Member`
|
"""Handles permission resolution for the :class:`~discord.Member`
|
||||||
or :class:`~discord.Role`.
|
or :class:`~discord.Role`.
|
||||||
@@ -340,13 +404,13 @@ 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(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
limit: int = 100,
|
limit: Optional[int] = 100,
|
||||||
check: Callable[[Message], bool] = MISSING,
|
check: Callable[[Message], bool] = MISSING,
|
||||||
before: Optional[SnowflakeTime] = None,
|
before: Optional[SnowflakeTime] = None,
|
||||||
after: Optional[SnowflakeTime] = None,
|
after: Optional[SnowflakeTime] = None,
|
||||||
@@ -466,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.
|
||||||
@@ -488,8 +553,11 @@ 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 to auto archive threads 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``.
|
||||||
slowmode_delay: :class:`int`
|
slowmode_delay: :class:`int`
|
||||||
Specifies the slowmode rate limit for user in this thread, in seconds.
|
Specifies the slowmode rate limit for user in this thread, in seconds.
|
||||||
@@ -501,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:
|
||||||
@@ -511,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
|
||||||
-------
|
-------
|
||||||
@@ -592,6 +667,28 @@ class Thread(Messageable, Hashable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.remove_user_from_thread(self.id, user.id)
|
await self._state.http.remove_user_from_thread(self.id, user.id)
|
||||||
|
|
||||||
|
async def fetch_members(self) -> List[ThreadMember]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves all :class:`ThreadMember` that are in this thread.
|
||||||
|
|
||||||
|
This requires :attr:`Intents.members` to get information about members
|
||||||
|
other than yourself.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Retrieving the members failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
List[:class:`ThreadMember`]
|
||||||
|
All thread members in the thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
members = await self._state.http.get_thread_members(self.id)
|
||||||
|
return [ThreadMember(parent=self, data=data) for data in members]
|
||||||
|
|
||||||
async def delete(self):
|
async def delete(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@@ -608,6 +705,29 @@ class Thread(Messageable, Hashable):
|
|||||||
"""
|
"""
|
||||||
await self._state.http.delete_channel(self.id)
|
await self._state.http.delete_channel(self.id)
|
||||||
|
|
||||||
|
def get_partial_message(self, message_id: int, /) -> PartialMessage:
|
||||||
|
"""Creates a :class:`PartialMessage` from the message ID.
|
||||||
|
|
||||||
|
This is useful if you want to work with a message and only have its ID without
|
||||||
|
doing an unnecessary API call.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
message_id: :class:`int`
|
||||||
|
The message ID to create a partial message for.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
:class:`PartialMessage`
|
||||||
|
The partial message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .message import PartialMessage
|
||||||
|
|
||||||
|
return PartialMessage(channel=self, id=message_id)
|
||||||
|
|
||||||
def _add_member(self, member: ThreadMember) -> None:
|
def _add_member(self, member: ThreadMember) -> None:
|
||||||
self._members[member.id] = member
|
self._members[member.id] = member
|
||||||
|
|
||||||
@@ -632,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.
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ class PartialPresenceUpdate(TypedDict):
|
|||||||
|
|
||||||
|
|
||||||
class ClientStatus(TypedDict, total=False):
|
class ClientStatus(TypedDict, total=False):
|
||||||
desktop: bool
|
desktop: str
|
||||||
mobile: bool
|
mobile: str
|
||||||
web: bool
|
web: str
|
||||||
|
|
||||||
|
|
||||||
class ActivityTimestamps(TypedDict, total=False):
|
class ActivityTimestamps(TypedDict, total=False):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from .user import User
|
|||||||
from .snowflake import Snowflake
|
from .snowflake import Snowflake
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .channel import ChannelType, VideoQualityMode, PermissionOverwrite
|
from .channel import ChannelType, VideoQualityMode, PermissionOverwrite
|
||||||
|
from .threads import Thread
|
||||||
|
|
||||||
AuditLogEvent = Literal[
|
AuditLogEvent = Literal[
|
||||||
1,
|
1,
|
||||||
@@ -69,19 +70,28 @@ AuditLogEvent = Literal[
|
|||||||
80,
|
80,
|
||||||
81,
|
81,
|
||||||
82,
|
82,
|
||||||
|
83,
|
||||||
|
84,
|
||||||
|
85,
|
||||||
|
90,
|
||||||
|
91,
|
||||||
|
92,
|
||||||
|
110,
|
||||||
|
111,
|
||||||
|
112,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class _AuditLogChange_Str(TypedDict):
|
class _AuditLogChange_Str(TypedDict):
|
||||||
key: Literal[
|
key: Literal[
|
||||||
'name', 'description', 'preferred_locale', 'vanity_url_code', 'topic', 'code', 'allow', 'deny', 'permissions'
|
'name', 'description', 'preferred_locale', 'vanity_url_code', 'topic', 'code', 'allow', 'deny', 'permissions', 'tags'
|
||||||
]
|
]
|
||||||
new_value: str
|
new_value: str
|
||||||
old_value: str
|
old_value: str
|
||||||
|
|
||||||
|
|
||||||
class _AuditLogChange_AssetHash(TypedDict):
|
class _AuditLogChange_AssetHash(TypedDict):
|
||||||
key: Literal['icon_hash', 'splash_hash', 'discovery_splash_hash', 'banner_hash', 'avatar_hash']
|
key: Literal['icon_hash', 'splash_hash', 'discovery_splash_hash', 'banner_hash', 'avatar_hash', 'asset']
|
||||||
new_value: str
|
new_value: str
|
||||||
old_value: str
|
old_value: str
|
||||||
|
|
||||||
@@ -98,6 +108,7 @@ class _AuditLogChange_Snowflake(TypedDict):
|
|||||||
'application_id',
|
'application_id',
|
||||||
'channel_id',
|
'channel_id',
|
||||||
'inviter_id',
|
'inviter_id',
|
||||||
|
'guild_id',
|
||||||
]
|
]
|
||||||
new_value: Snowflake
|
new_value: Snowflake
|
||||||
old_value: Snowflake
|
old_value: Snowflake
|
||||||
@@ -116,6 +127,9 @@ class _AuditLogChange_Bool(TypedDict):
|
|||||||
'enabled_emoticons',
|
'enabled_emoticons',
|
||||||
'region',
|
'region',
|
||||||
'rtc_region',
|
'rtc_region',
|
||||||
|
'available',
|
||||||
|
'archived',
|
||||||
|
'locked',
|
||||||
]
|
]
|
||||||
new_value: bool
|
new_value: bool
|
||||||
old_value: bool
|
old_value: bool
|
||||||
@@ -132,6 +146,8 @@ class _AuditLogChange_Int(TypedDict):
|
|||||||
'max_uses',
|
'max_uses',
|
||||||
'max_age',
|
'max_age',
|
||||||
'user_limit',
|
'user_limit',
|
||||||
|
'auto_archive_duration',
|
||||||
|
'default_auto_archive_duration',
|
||||||
]
|
]
|
||||||
new_value: int
|
new_value: int
|
||||||
old_value: int
|
old_value: int
|
||||||
@@ -238,3 +254,4 @@ class AuditLog(TypedDict):
|
|||||||
users: List[User]
|
users: List[User]
|
||||||
audit_log_entries: List[AuditLogEntry]
|
audit_log_entries: List[AuditLogEntry]
|
||||||
integrations: List[PartialIntegration]
|
integrations: List[PartialIntegration]
|
||||||
|
threads: List[Thread]
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
from typing import List, Literal, Optional, TypedDict, Union
|
from typing import List, Literal, Optional, TypedDict, Union
|
||||||
from .user import PartialUser
|
from .user import PartialUser
|
||||||
from .snowflake import Snowflake
|
from .snowflake import Snowflake
|
||||||
from .threads import ThreadMetadata, ThreadMember
|
from .threads import ThreadMetadata, ThreadMember, ThreadArchiveDuration
|
||||||
|
|
||||||
|
|
||||||
OverwriteType = Literal[0, 1]
|
OverwriteType = Literal[0, 1]
|
||||||
@@ -63,6 +63,7 @@ class _TextChannelOptional(TypedDict, total=False):
|
|||||||
last_message_id: Optional[Snowflake]
|
last_message_id: Optional[Snowflake]
|
||||||
last_pin_timestamp: str
|
last_pin_timestamp: str
|
||||||
rate_limit_per_user: int
|
rate_limit_per_user: int
|
||||||
|
default_auto_archive_duration: ThreadArchiveDuration
|
||||||
|
|
||||||
|
|
||||||
class TextChannel(_BaseGuildChannel, _TextChannelOptional):
|
class TextChannel(_BaseGuildChannel, _TextChannelOptional):
|
||||||
@@ -78,13 +79,13 @@ VideoQualityMode = Literal[1, 2]
|
|||||||
|
|
||||||
class _VoiceChannelOptional(TypedDict, total=False):
|
class _VoiceChannelOptional(TypedDict, total=False):
|
||||||
rtc_region: Optional[str]
|
rtc_region: Optional[str]
|
||||||
bitrate: int
|
|
||||||
user_limit: int
|
|
||||||
video_quality_mode: VideoQualityMode
|
video_quality_mode: VideoQualityMode
|
||||||
|
|
||||||
|
|
||||||
class VoiceChannel(_BaseGuildChannel, _VoiceChannelOptional):
|
class VoiceChannel(_BaseGuildChannel, _VoiceChannelOptional):
|
||||||
type: Literal[2]
|
type: Literal[2]
|
||||||
|
bitrate: int
|
||||||
|
user_limit: int
|
||||||
|
|
||||||
|
|
||||||
class CategoryChannel(_BaseGuildChannel):
|
class CategoryChannel(_BaseGuildChannel):
|
||||||
@@ -97,13 +98,13 @@ class StoreChannel(_BaseGuildChannel):
|
|||||||
|
|
||||||
class _StageChannelOptional(TypedDict, total=False):
|
class _StageChannelOptional(TypedDict, total=False):
|
||||||
rtc_region: Optional[str]
|
rtc_region: Optional[str]
|
||||||
bitrate: int
|
|
||||||
user_limit: int
|
|
||||||
topic: str
|
topic: str
|
||||||
|
|
||||||
|
|
||||||
class StageChannel(_BaseGuildChannel, _StageChannelOptional):
|
class StageChannel(_BaseGuildChannel, _StageChannelOptional):
|
||||||
type: Literal[13]
|
type: Literal[13]
|
||||||
|
bitrate: int
|
||||||
|
user_limit: int
|
||||||
|
|
||||||
|
|
||||||
class _ThreadChannelOptional(TypedDict, total=False):
|
class _ThreadChannelOptional(TypedDict, total=False):
|
||||||
@@ -115,7 +116,7 @@ class _ThreadChannelOptional(TypedDict, total=False):
|
|||||||
|
|
||||||
|
|
||||||
class ThreadChannel(_BaseChannel, _ThreadChannelOptional):
|
class ThreadChannel(_BaseChannel, _ThreadChannelOptional):
|
||||||
type: Literal[11, 12]
|
type: Literal[10, 11, 12]
|
||||||
guild_id: Snowflake
|
guild_id: Snowflake
|
||||||
parent_id: Snowflake
|
parent_id: Snowflake
|
||||||
owner_id: Snowflake
|
owner_id: Snowflake
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class _SelectMenuOptional(TypedDict, total=False):
|
|||||||
placeholder: str
|
placeholder: str
|
||||||
min_values: int
|
min_values: int
|
||||||
max_values: int
|
max_values: int
|
||||||
|
disabled: bool
|
||||||
|
|
||||||
|
|
||||||
class _SelectOptionsOptional(TypedDict, total=False):
|
class _SelectOptionsOptional(TypedDict, total=False):
|
||||||
|
|||||||
@@ -75,21 +75,28 @@ VerificationLevel = Literal[0, 1, 2, 3, 4]
|
|||||||
NSFWLevel = Literal[0, 1, 2, 3]
|
NSFWLevel = Literal[0, 1, 2, 3]
|
||||||
PremiumTier = Literal[0, 1, 2, 3]
|
PremiumTier = Literal[0, 1, 2, 3]
|
||||||
GuildFeature = Literal[
|
GuildFeature = Literal[
|
||||||
'INVITE_SPLASH',
|
|
||||||
'VIP_REGIONS',
|
|
||||||
'VANITY_URL',
|
|
||||||
'VERIFIED',
|
|
||||||
'PARTNERED',
|
|
||||||
'COMMUNITY',
|
|
||||||
'COMMERCE',
|
|
||||||
'NEWS',
|
|
||||||
'DISCOVERABLE',
|
|
||||||
'FEATURABLE',
|
|
||||||
'ANIMATED_ICON',
|
'ANIMATED_ICON',
|
||||||
'BANNER',
|
'BANNER',
|
||||||
'WELCOME_SCREEN_ENABLED',
|
'COMMERCE',
|
||||||
|
'COMMUNITY',
|
||||||
|
'DISCOVERABLE',
|
||||||
|
'FEATURABLE',
|
||||||
|
'INVITE_SPLASH',
|
||||||
'MEMBER_VERIFICATION_GATE_ENABLED',
|
'MEMBER_VERIFICATION_GATE_ENABLED',
|
||||||
|
'MONETIZATION_ENABLED',
|
||||||
|
'MORE_EMOJI',
|
||||||
|
'MORE_STICKERS',
|
||||||
|
'NEWS',
|
||||||
|
'PARTNERED',
|
||||||
'PREVIEW_ENABLED',
|
'PREVIEW_ENABLED',
|
||||||
|
'PRIVATE_THREADS',
|
||||||
|
'SEVEN_DAY_THREAD_ARCHIVE',
|
||||||
|
'THREE_DAY_THREAD_ARCHIVE',
|
||||||
|
'TICKETED_EVENTS_ENABLED',
|
||||||
|
'VANITY_URL',
|
||||||
|
'VERIFIED',
|
||||||
|
'VIP_REGIONS',
|
||||||
|
'WELCOME_SCREEN_ENABLED',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -152,8 +159,10 @@ class ChannelPositionUpdate(TypedDict):
|
|||||||
lock_permissions: Optional[bool]
|
lock_permissions: Optional[bool]
|
||||||
parent_id: Optional[Snowflake]
|
parent_id: Optional[Snowflake]
|
||||||
|
|
||||||
|
|
||||||
class _RolePositionRequired(TypedDict):
|
class _RolePositionRequired(TypedDict):
|
||||||
id: Snowflake
|
id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
class RolePositionUpdate(_RolePositionRequired, total=False):
|
class RolePositionUpdate(_RolePositionRequired, total=False):
|
||||||
position: Optional[Snowflake]
|
position: Optional[Snowflake]
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class BaseIntegration(PartialIntegration):
|
|||||||
|
|
||||||
|
|
||||||
class StreamIntegration(BaseIntegration):
|
class StreamIntegration(BaseIntegration):
|
||||||
role_id: Snowflake
|
role_id: Optional[Snowflake]
|
||||||
enable_emoticons: bool
|
enable_emoticons: bool
|
||||||
subscriber_count: int
|
subscriber_count: int
|
||||||
revoked: bool
|
revoked: bool
|
||||||
|
|||||||
@@ -37,8 +37,11 @@ if TYPE_CHECKING:
|
|||||||
from .message import AllowedMentions, Message
|
from .message import AllowedMentions, Message
|
||||||
|
|
||||||
|
|
||||||
|
ApplicationCommandType = Literal[1, 2, 3]
|
||||||
|
|
||||||
class _ApplicationCommandOptional(TypedDict, total=False):
|
class _ApplicationCommandOptional(TypedDict, total=False):
|
||||||
options: List[ApplicationCommandOption]
|
options: List[ApplicationCommandOption]
|
||||||
|
type: ApplicationCommandType
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCommand(_ApplicationCommandOptional):
|
class ApplicationCommand(_ApplicationCommandOptional):
|
||||||
@@ -53,7 +56,7 @@ class _ApplicationCommandOptionOptional(TypedDict, total=False):
|
|||||||
options: List[ApplicationCommandOption]
|
options: List[ApplicationCommandOption]
|
||||||
|
|
||||||
|
|
||||||
ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCommandOption(_ApplicationCommandOptionOptional):
|
class ApplicationCommandOption(_ApplicationCommandOptionOptional):
|
||||||
@@ -93,16 +96,48 @@ class GuildApplicationCommandPermissions(PartialGuildApplicationCommandPermissio
|
|||||||
InteractionType = Literal[1, 2, 3]
|
InteractionType = Literal[1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
class _ApplicationCommandInteractionDataOptionOptional(TypedDict, total=False):
|
class _ApplicationCommandInteractionDataOption(TypedDict):
|
||||||
value: ApplicationCommandOptionType
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class _ApplicationCommandInteractionDataOptionSubcommand(_ApplicationCommandInteractionDataOption):
|
||||||
|
type: Literal[1, 2]
|
||||||
options: List[ApplicationCommandInteractionDataOption]
|
options: List[ApplicationCommandInteractionDataOption]
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCommandInteractionDataOption(
|
class _ApplicationCommandInteractionDataOptionString(_ApplicationCommandInteractionDataOption):
|
||||||
_ApplicationCommandInteractionDataOptionOptional
|
type: Literal[3]
|
||||||
):
|
value: str
|
||||||
name: str
|
|
||||||
type: ApplicationCommandOptionType
|
|
||||||
|
class _ApplicationCommandInteractionDataOptionInteger(_ApplicationCommandInteractionDataOption):
|
||||||
|
type: Literal[4]
|
||||||
|
value: int
|
||||||
|
|
||||||
|
|
||||||
|
class _ApplicationCommandInteractionDataOptionBoolean(_ApplicationCommandInteractionDataOption):
|
||||||
|
type: Literal[5]
|
||||||
|
value: bool
|
||||||
|
|
||||||
|
|
||||||
|
class _ApplicationCommandInteractionDataOptionSnowflake(_ApplicationCommandInteractionDataOption):
|
||||||
|
type: Literal[6, 7, 8, 9]
|
||||||
|
value: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class _ApplicationCommandInteractionDataOptionNumber(_ApplicationCommandInteractionDataOption):
|
||||||
|
type: Literal[10]
|
||||||
|
value: float
|
||||||
|
|
||||||
|
|
||||||
|
ApplicationCommandInteractionDataOption = Union[
|
||||||
|
_ApplicationCommandInteractionDataOptionString,
|
||||||
|
_ApplicationCommandInteractionDataOptionInteger,
|
||||||
|
_ApplicationCommandInteractionDataOptionSubcommand,
|
||||||
|
_ApplicationCommandInteractionDataOptionBoolean,
|
||||||
|
_ApplicationCommandInteractionDataOptionSnowflake,
|
||||||
|
_ApplicationCommandInteractionDataOptionNumber,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCommandResolvedPartialChannel(TypedDict):
|
class ApplicationCommandResolvedPartialChannel(TypedDict):
|
||||||
@@ -122,6 +157,8 @@ class ApplicationCommandInteractionDataResolved(TypedDict, total=False):
|
|||||||
class _ApplicationCommandInteractionDataOptional(TypedDict, total=False):
|
class _ApplicationCommandInteractionDataOptional(TypedDict, total=False):
|
||||||
options: List[ApplicationCommandInteractionDataOption]
|
options: List[ApplicationCommandInteractionDataOption]
|
||||||
resolved: ApplicationCommandInteractionDataResolved
|
resolved: ApplicationCommandInteractionDataResolved
|
||||||
|
target_id: Snowflake
|
||||||
|
type: ApplicationCommandType
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCommandInteractionData(_ApplicationCommandInteractionDataOptional):
|
class ApplicationCommandInteractionData(_ApplicationCommandInteractionDataOptional):
|
||||||
@@ -138,8 +175,11 @@ class ComponentInteractionData(_ComponentInteractionDataOptional):
|
|||||||
component_type: ComponentType
|
component_type: ComponentType
|
||||||
|
|
||||||
|
|
||||||
|
InteractionData = Union[ApplicationCommandInteractionData, ComponentInteractionData]
|
||||||
|
|
||||||
|
|
||||||
class _InteractionOptional(TypedDict, total=False):
|
class _InteractionOptional(TypedDict, total=False):
|
||||||
data: Union[ApplicationCommandInteractionData, ComponentInteractionData]
|
data: InteractionData
|
||||||
guild_id: Snowflake
|
guild_id: Snowflake
|
||||||
channel_id: Snowflake
|
channel_id: Snowflake
|
||||||
member: Member
|
member: Member
|
||||||
@@ -182,8 +222,15 @@ class MessageInteraction(TypedDict):
|
|||||||
user: User
|
user: User
|
||||||
|
|
||||||
|
|
||||||
class EditApplicationCommand(TypedDict):
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
class _EditApplicationCommandOptional(TypedDict, total=False):
|
||||||
description: str
|
description: str
|
||||||
options: Optional[List[ApplicationCommandOption]]
|
options: Optional[List[ApplicationCommandOption]]
|
||||||
|
type: ApplicationCommandType
|
||||||
|
|
||||||
|
|
||||||
|
class EditApplicationCommand(_EditApplicationCommandOptional):
|
||||||
|
name: str
|
||||||
default_permission: bool
|
default_permission: bool
|
||||||
|
|||||||
@@ -39,8 +39,25 @@ 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
|
||||||
pending: bool
|
pending: bool
|
||||||
permissions: str
|
permissions: str
|
||||||
|
|
||||||
|
|
||||||
|
class _OptionalMemberWithUser(PartialMember, total=False):
|
||||||
|
avatar: str
|
||||||
|
nick: str
|
||||||
|
premium_since: str
|
||||||
|
pending: bool
|
||||||
|
permissions: str
|
||||||
|
|
||||||
|
|
||||||
|
class MemberWithUser(_OptionalMemberWithUser):
|
||||||
|
user: User
|
||||||
|
|
||||||
|
|
||||||
|
class UserWithMember(User, total=False):
|
||||||
|
member: _OptionalMemberWithUser
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import List, Literal, Optional, TypedDict, Union
|
from typing import List, Literal, Optional, TypedDict, Union
|
||||||
from .snowflake import Snowflake, SnowflakeList
|
from .snowflake import Snowflake, SnowflakeList
|
||||||
from .member import Member
|
from .member import Member, UserWithMember
|
||||||
from .user import User
|
from .user import User
|
||||||
from .emoji import PartialEmoji
|
from .emoji import PartialEmoji
|
||||||
from .embed import Embed
|
from .embed import Embed
|
||||||
from .channel import ChannelType
|
from .channel import ChannelType
|
||||||
from .components import Component
|
from .components import Component
|
||||||
from .interactions import MessageInteraction
|
from .interactions import MessageInteraction
|
||||||
|
from .sticker import StickerItem
|
||||||
|
|
||||||
|
|
||||||
class ChannelMention(TypedDict):
|
class ChannelMention(TypedDict):
|
||||||
@@ -89,22 +90,6 @@ class MessageReference(TypedDict, total=False):
|
|||||||
fail_if_not_exists: bool
|
fail_if_not_exists: bool
|
||||||
|
|
||||||
|
|
||||||
class _StickerOptional(TypedDict, total=False):
|
|
||||||
tags: str
|
|
||||||
|
|
||||||
|
|
||||||
StickerFormatType = Literal[1, 2, 3]
|
|
||||||
|
|
||||||
|
|
||||||
class Sticker(_StickerOptional):
|
|
||||||
id: Snowflake
|
|
||||||
pack_id: Snowflake
|
|
||||||
name: str
|
|
||||||
description: str
|
|
||||||
asset: str
|
|
||||||
format_type: StickerFormatType
|
|
||||||
|
|
||||||
|
|
||||||
class _MessageOptional(TypedDict, total=False):
|
class _MessageOptional(TypedDict, total=False):
|
||||||
guild_id: Snowflake
|
guild_id: Snowflake
|
||||||
member: Member
|
member: Member
|
||||||
@@ -117,7 +102,7 @@ class _MessageOptional(TypedDict, total=False):
|
|||||||
application_id: Snowflake
|
application_id: Snowflake
|
||||||
message_reference: MessageReference
|
message_reference: MessageReference
|
||||||
flags: int
|
flags: int
|
||||||
stickers: List[Sticker]
|
sticker_items: List[StickerItem]
|
||||||
referenced_message: Optional[Message]
|
referenced_message: Optional[Message]
|
||||||
interaction: MessageInteraction
|
interaction: MessageInteraction
|
||||||
components: List[Component]
|
components: List[Component]
|
||||||
@@ -135,7 +120,7 @@ class Message(_MessageOptional):
|
|||||||
edited_timestamp: Optional[str]
|
edited_timestamp: Optional[str]
|
||||||
tts: bool
|
tts: bool
|
||||||
mention_everyone: bool
|
mention_everyone: bool
|
||||||
mentions: List[User]
|
mentions: List[UserWithMember]
|
||||||
mention_roles: SnowflakeList
|
mention_roles: SnowflakeList
|
||||||
attachments: List[Attachment]
|
attachments: List[Attachment]
|
||||||
embeds: List[Embed]
|
embeds: List[Embed]
|
||||||
|
|||||||
87
discord/types/raw_models.py
Normal file
87
discord/types/raw_models.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import TypedDict, List
|
||||||
|
from .snowflake import Snowflake
|
||||||
|
from .member import Member
|
||||||
|
from .emoji import PartialEmoji
|
||||||
|
|
||||||
|
|
||||||
|
class _MessageEventOptional(TypedDict, total=False):
|
||||||
|
guild_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDeleteEvent(_MessageEventOptional):
|
||||||
|
id: Snowflake
|
||||||
|
channel_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class BulkMessageDeleteEvent(_MessageEventOptional):
|
||||||
|
ids: List[Snowflake]
|
||||||
|
channel_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class _ReactionActionEventOptional(TypedDict, total=False):
|
||||||
|
guild_id: Snowflake
|
||||||
|
member: Member
|
||||||
|
|
||||||
|
|
||||||
|
class MessageUpdateEvent(_MessageEventOptional):
|
||||||
|
id: Snowflake
|
||||||
|
channel_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionActionEvent(_ReactionActionEventOptional):
|
||||||
|
user_id: Snowflake
|
||||||
|
channel_id: Snowflake
|
||||||
|
message_id: Snowflake
|
||||||
|
emoji: PartialEmoji
|
||||||
|
|
||||||
|
|
||||||
|
class _ReactionClearEventOptional(TypedDict, total=False):
|
||||||
|
guild_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionClearEvent(_ReactionClearEventOptional):
|
||||||
|
channel_id: Snowflake
|
||||||
|
message_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class _ReactionClearEmojiEventOptional(TypedDict, total=False):
|
||||||
|
guild_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class ReactionClearEmojiEvent(_ReactionClearEmojiEventOptional):
|
||||||
|
channel_id: int
|
||||||
|
message_id: int
|
||||||
|
emoji: PartialEmoji
|
||||||
|
|
||||||
|
|
||||||
|
class _IntegrationDeleteEventOptional(TypedDict, total=False):
|
||||||
|
application_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrationDeleteEvent(_IntegrationDeleteEventOptional):
|
||||||
|
id: Snowflake
|
||||||
|
guild_id: Snowflake
|
||||||
93
discord/types/sticker.py
Normal file
93
discord/types/sticker.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Literal, TypedDict, Union
|
||||||
|
from .snowflake import Snowflake
|
||||||
|
from .user import User
|
||||||
|
|
||||||
|
StickerFormatType = Literal[1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
class StickerItem(TypedDict):
|
||||||
|
id: Snowflake
|
||||||
|
name: str
|
||||||
|
format_type: StickerFormatType
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSticker(TypedDict):
|
||||||
|
id: Snowflake
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
tags: str
|
||||||
|
format_type: StickerFormatType
|
||||||
|
|
||||||
|
|
||||||
|
class StandardSticker(BaseSticker):
|
||||||
|
type: Literal[1]
|
||||||
|
sort_value: int
|
||||||
|
pack_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class _GuildStickerOptional(TypedDict, total=False):
|
||||||
|
user: User
|
||||||
|
|
||||||
|
|
||||||
|
class GuildSticker(BaseSticker, _GuildStickerOptional):
|
||||||
|
type: Literal[2]
|
||||||
|
available: bool
|
||||||
|
guild_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
Sticker = Union[BaseSticker, StandardSticker, GuildSticker]
|
||||||
|
|
||||||
|
|
||||||
|
class StickerPack(TypedDict):
|
||||||
|
id: Snowflake
|
||||||
|
stickers: List[StandardSticker]
|
||||||
|
name: str
|
||||||
|
sku_id: Snowflake
|
||||||
|
cover_sticker_id: Snowflake
|
||||||
|
description: str
|
||||||
|
banner_asset_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
|
class _CreateGuildStickerOptional(TypedDict, total=False):
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class CreateGuildSticker(_CreateGuildStickerOptional):
|
||||||
|
name: str
|
||||||
|
tags: str
|
||||||
|
|
||||||
|
|
||||||
|
class EditGuildSticker(TypedDict, total=False):
|
||||||
|
name: str
|
||||||
|
tags: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class ListPremiumStickerPacks(TypedDict):
|
||||||
|
sticker_packs: List[StickerPack]
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -22,13 +22,16 @@ 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 Optional, TypedDict
|
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']
|
||||||
|
|
||||||
|
|
||||||
class _PartialVoiceStateOptional(TypedDict, total=False):
|
class _PartialVoiceStateOptional(TypedDict, total=False):
|
||||||
member: Member
|
member: MemberWithUser
|
||||||
self_stream: bool
|
self_stream: bool
|
||||||
|
|
||||||
|
|
||||||
@@ -59,3 +62,24 @@ class VoiceRegion(TypedDict):
|
|||||||
optimal: bool
|
optimal: bool
|
||||||
deprecated: bool
|
deprecated: bool
|
||||||
custom: bool
|
custom: bool
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceServerUpdate(TypedDict):
|
||||||
|
token: str
|
||||||
|
guild_id: Snowflake
|
||||||
|
endpoint: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceIdentify(TypedDict):
|
||||||
|
server_id: Snowflake
|
||||||
|
user_id: Snowflake
|
||||||
|
session_id: str
|
||||||
|
token: str
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceReady(TypedDict):
|
||||||
|
ssrc: int
|
||||||
|
ip: str
|
||||||
|
port: int
|
||||||
|
modes: List[SupportedModes]
|
||||||
|
heartbeat_interval: int
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Button(Item[V]):
|
|||||||
Whether the button is disabled or not.
|
Whether the button is disabled or not.
|
||||||
label: Optional[:class:`str`]
|
label: Optional[:class:`str`]
|
||||||
The label of the button, if any.
|
The label of the button, if any.
|
||||||
emoji: Optional[Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`]]
|
emoji: Optional[Union[:class:`.PartialEmoji`, :class:`.Emoji`, :class:`str`]]
|
||||||
The emoji of the button, if available.
|
The emoji of the button, if available.
|
||||||
row: Optional[:class:`int`]
|
row: Optional[:class:`int`]
|
||||||
The relative row this button belongs to. A Discord component can only have 5
|
The relative row this button belongs to. A Discord component can only have 5
|
||||||
@@ -87,7 +87,7 @@ class Button(Item[V]):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
style: ButtonStyle,
|
style: ButtonStyle = ButtonStyle.secondary,
|
||||||
label: Optional[str] = None,
|
label: Optional[str] = None,
|
||||||
disabled: bool = False,
|
disabled: bool = False,
|
||||||
custom_id: Optional[str] = None,
|
custom_id: Optional[str] = None,
|
||||||
@@ -180,7 +180,7 @@ class Button(Item[V]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def emoji(self) -> Optional[PartialEmoji]:
|
def emoji(self) -> Optional[PartialEmoji]:
|
||||||
"""Optional[:class:`PartialEmoji`]: The emoji of the button, if available."""
|
"""Optional[:class:`.PartialEmoji`]: The emoji of the button, if available."""
|
||||||
return self._underlying.emoji
|
return self._underlying.emoji
|
||||||
|
|
||||||
@emoji.setter
|
@emoji.setter
|
||||||
@@ -217,6 +217,11 @@ class Button(Item[V]):
|
|||||||
def is_dispatchable(self) -> bool:
|
def is_dispatchable(self) -> bool:
|
||||||
return self.custom_id is not None
|
return self.custom_id is not None
|
||||||
|
|
||||||
|
def is_persistent(self) -> bool:
|
||||||
|
if self.style is ButtonStyle.link:
|
||||||
|
return self.url is not None
|
||||||
|
return super().is_persistent()
|
||||||
|
|
||||||
def refresh_component(self, button: ButtonComponent) -> None:
|
def refresh_component(self, button: ButtonComponent) -> None:
|
||||||
self._underlying = button
|
self._underlying = button
|
||||||
|
|
||||||
@@ -251,13 +256,13 @@ def button(
|
|||||||
custom_id: Optional[:class:`str`]
|
custom_id: Optional[:class:`str`]
|
||||||
The ID of the button that gets received during an interaction.
|
The ID of the button that gets received during an interaction.
|
||||||
It is recommended not to set this parameter to prevent conflicts.
|
It is recommended not to set this parameter to prevent conflicts.
|
||||||
style: :class:`ButtonStyle`
|
style: :class:`.ButtonStyle`
|
||||||
The style of the button. Defaults to :attr:`ButtonStyle.grey`.
|
The style of the button. Defaults to :attr:`.ButtonStyle.grey`.
|
||||||
disabled: :class:`bool`
|
disabled: :class:`bool`
|
||||||
Whether the button is disabled or not. Defaults to ``False``.
|
Whether the button is disabled or not. Defaults to ``False``.
|
||||||
emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]]
|
emoji: Optional[Union[:class:`str`, :class:`.Emoji`, :class:`.PartialEmoji`]]
|
||||||
The emoji of the button. This can be in string form or a :class:`PartialEmoji`
|
The emoji of the button. This can be in string form or a :class:`.PartialEmoji`
|
||||||
or a full :class:`Emoji`.
|
or a full :class:`.Emoji`.
|
||||||
row: Optional[:class:`int`]
|
row: Optional[:class:`int`]
|
||||||
The relative row this button belongs to. A Discord component can only have 5
|
The relative row this button belongs to. A Discord component can only have 5
|
||||||
rows. By default, items are arranged automatically into those 5 rows. If you'd
|
rows. By default, items are arranged automatically into those 5 rows. If you'd
|
||||||
@@ -267,11 +272,9 @@ def button(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func: ItemCallbackType) -> ItemCallbackType:
|
def decorator(func: ItemCallbackType) -> ItemCallbackType:
|
||||||
nonlocal custom_id
|
|
||||||
if not inspect.iscoroutinefunction(func):
|
if not inspect.iscoroutinefunction(func):
|
||||||
raise TypeError('button function must be a coroutine function')
|
raise TypeError('button function must be a coroutine function')
|
||||||
|
|
||||||
custom_id = custom_id or os.urandom(32).hex()
|
|
||||||
func.__discord_ui_model_type__ = Button
|
func.__discord_ui_model_type__ = Button
|
||||||
func.__discord_ui_model_kwargs__ = {
|
func.__discord_ui_model_kwargs__ = {
|
||||||
'style': style,
|
'style': style,
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class Item(Generic[V]):
|
|||||||
The current UI items supported are:
|
The current UI items supported are:
|
||||||
|
|
||||||
- :class:`discord.ui.Button`
|
- :class:`discord.ui.Button`
|
||||||
|
- :class:`discord.ui.Select`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__item_repr_attributes__: Tuple[str, ...] = ('row',)
|
__item_repr_attributes__: Tuple[str, ...] = ('row',)
|
||||||
@@ -106,7 +109,6 @@ class Item(Generic[V]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self) -> int:
|
def width(self) -> int:
|
||||||
""":class:`int`: The width of the item."""
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -123,7 +125,7 @@ class Item(Generic[V]):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
interaction: :class:`Interaction`
|
interaction: :class:`.Interaction`
|
||||||
The interaction that triggered this UI item.
|
The interaction that triggered this UI item.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ class Select(Item[V]):
|
|||||||
|
|
||||||
This is usually represented as a drop down menu.
|
This is usually represented as a drop down menu.
|
||||||
|
|
||||||
|
In order to get the selected items that the user has chosen, use :attr:`Select.values`.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -76,6 +78,8 @@ class Select(Item[V]):
|
|||||||
Defaults to 1 and must be between 1 and 25.
|
Defaults to 1 and must be between 1 and 25.
|
||||||
options: List[:class:`discord.SelectOption`]
|
options: List[:class:`discord.SelectOption`]
|
||||||
A list of options that can be selected in this menu.
|
A list of options that can be selected in this menu.
|
||||||
|
disabled: :class:`bool`
|
||||||
|
Whether the select is disabled or not.
|
||||||
row: Optional[:class:`int`]
|
row: Optional[:class:`int`]
|
||||||
The relative row this select menu belongs to. A Discord component can only have 5
|
The relative row this select menu belongs to. A Discord component can only have 5
|
||||||
rows. By default, items are arranged automatically into those 5 rows. If you'd
|
rows. By default, items are arranged automatically into those 5 rows. If you'd
|
||||||
@@ -89,6 +93,7 @@ class Select(Item[V]):
|
|||||||
'min_values',
|
'min_values',
|
||||||
'max_values',
|
'max_values',
|
||||||
'options',
|
'options',
|
||||||
|
'disabled',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -99,8 +104,10 @@ class Select(Item[V]):
|
|||||||
min_values: int = 1,
|
min_values: int = 1,
|
||||||
max_values: int = 1,
|
max_values: int = 1,
|
||||||
options: List[SelectOption] = MISSING,
|
options: List[SelectOption] = MISSING,
|
||||||
|
disabled: bool = False,
|
||||||
row: Optional[int] = None,
|
row: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
self._selected_values: List[str] = []
|
self._selected_values: List[str] = []
|
||||||
self._provided_custom_id = custom_id is not MISSING
|
self._provided_custom_id = custom_id is not MISSING
|
||||||
custom_id = os.urandom(16).hex() if custom_id is MISSING else custom_id
|
custom_id = os.urandom(16).hex() if custom_id is MISSING else custom_id
|
||||||
@@ -112,6 +119,7 @@ class Select(Item[V]):
|
|||||||
min_values=min_values,
|
min_values=min_values,
|
||||||
max_values=max_values,
|
max_values=max_values,
|
||||||
options=options,
|
options=options,
|
||||||
|
disabled=disabled,
|
||||||
)
|
)
|
||||||
self.row = row
|
self.row = row
|
||||||
|
|
||||||
@@ -189,16 +197,16 @@ 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`.
|
||||||
default: :class:`bool`
|
default: :class:`bool`
|
||||||
Whether this option is selected by default.
|
Whether this option is selected by default.
|
||||||
|
|
||||||
@@ -238,6 +246,15 @@ class Select(Item[V]):
|
|||||||
|
|
||||||
self._underlying.options.append(option)
|
self._underlying.options.append(option)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def disabled(self) -> bool:
|
||||||
|
""":class:`bool`: Whether the select is disabled or not."""
|
||||||
|
return self._underlying.disabled
|
||||||
|
|
||||||
|
@disabled.setter
|
||||||
|
def disabled(self, value: bool):
|
||||||
|
self._underlying.disabled = bool(value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def values(self) -> List[str]:
|
def values(self) -> List[str]:
|
||||||
"""List[:class:`str`]: A list of values that have been selected by the user."""
|
"""List[:class:`str`]: A list of values that have been selected by the user."""
|
||||||
@@ -265,6 +282,7 @@ class Select(Item[V]):
|
|||||||
min_values=component.min_values,
|
min_values=component.min_values,
|
||||||
max_values=component.max_values,
|
max_values=component.max_values,
|
||||||
options=component.options,
|
options=component.options,
|
||||||
|
disabled=component.disabled,
|
||||||
row=None,
|
row=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -283,6 +301,7 @@ def select(
|
|||||||
min_values: int = 1,
|
min_values: int = 1,
|
||||||
max_values: int = 1,
|
max_values: int = 1,
|
||||||
options: List[SelectOption] = MISSING,
|
options: List[SelectOption] = MISSING,
|
||||||
|
disabled: bool = False,
|
||||||
row: Optional[int] = None,
|
row: Optional[int] = None,
|
||||||
) -> Callable[[ItemCallbackType], ItemCallbackType]:
|
) -> Callable[[ItemCallbackType], ItemCallbackType]:
|
||||||
"""A decorator that attaches a select menu to a component.
|
"""A decorator that attaches a select menu to a component.
|
||||||
@@ -291,6 +310,8 @@ def select(
|
|||||||
the :class:`discord.ui.View`, the :class:`discord.ui.Select` being pressed and
|
the :class:`discord.ui.View`, the :class:`discord.ui.Select` being pressed and
|
||||||
the :class:`discord.Interaction` you receive.
|
the :class:`discord.Interaction` you receive.
|
||||||
|
|
||||||
|
In order to get the selected items that the user has chosen within the callback
|
||||||
|
use :attr:`Select.values`.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
@@ -313,11 +334,13 @@ def select(
|
|||||||
Defaults to 1 and must be between 1 and 25.
|
Defaults to 1 and must be between 1 and 25.
|
||||||
options: List[:class:`discord.SelectOption`]
|
options: List[:class:`discord.SelectOption`]
|
||||||
A list of options that can be selected in this menu.
|
A list of options that can be selected in this menu.
|
||||||
|
disabled: :class:`bool`
|
||||||
|
Whether the select is disabled or not. Defaults to ``False``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func: ItemCallbackType) -> ItemCallbackType:
|
def decorator(func: ItemCallbackType) -> ItemCallbackType:
|
||||||
if not inspect.iscoroutinefunction(func):
|
if not inspect.iscoroutinefunction(func):
|
||||||
raise TypeError('button function must be a coroutine function')
|
raise TypeError('select function must be a coroutine function')
|
||||||
|
|
||||||
func.__discord_ui_model_type__ = Select
|
func.__discord_ui_model_type__ = Select
|
||||||
func.__discord_ui_model_kwargs__ = {
|
func.__discord_ui_model_kwargs__ = {
|
||||||
@@ -327,6 +350,7 @@ def select(
|
|||||||
'min_values': min_values,
|
'min_values': min_values,
|
||||||
'max_values': max_values,
|
'max_values': max_values,
|
||||||
'options': options,
|
'options': options,
|
||||||
|
'disabled': disabled,
|
||||||
}
|
}
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
from .item import Item, ItemCallbackType
|
from .item import Item, ItemCallbackType
|
||||||
from ..enums import ComponentType
|
|
||||||
from ..components import (
|
from ..components import (
|
||||||
Component,
|
Component,
|
||||||
ActionRow as ActionRowComponent,
|
ActionRow as ActionRowComponent,
|
||||||
_component_factory,
|
_component_factory,
|
||||||
Button as ButtonComponent,
|
Button as ButtonComponent,
|
||||||
|
SelectMenu as SelectComponent,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@@ -48,6 +48,7 @@ __all__ = (
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..interactions import Interaction
|
from ..interactions import Interaction
|
||||||
|
from ..message import Message
|
||||||
from ..types.components import Component as ComponentPayload
|
from ..types.components import Component as ComponentPayload
|
||||||
from ..state import ConnectionState
|
from ..state import ConnectionState
|
||||||
|
|
||||||
@@ -65,6 +66,10 @@ def _component_to_item(component: Component) -> Item:
|
|||||||
from .button import Button
|
from .button import Button
|
||||||
|
|
||||||
return Button.from_component(component)
|
return Button.from_component(component)
|
||||||
|
if isinstance(component, SelectComponent):
|
||||||
|
from .select import Select
|
||||||
|
|
||||||
|
return Select.from_component(component)
|
||||||
return Item.from_component(component)
|
return Item.from_component(component)
|
||||||
|
|
||||||
|
|
||||||
@@ -109,15 +114,18 @@ class _ViewWeights:
|
|||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
self.weights = [0, 0, 0, 0, 0]
|
self.weights = [0, 0, 0, 0, 0]
|
||||||
|
|
||||||
|
|
||||||
class View:
|
class View:
|
||||||
"""Represents a UI view.
|
"""Represents a UI view.
|
||||||
|
|
||||||
This object must be inherited to create a UI within Discord.
|
This object must be inherited to create a UI within Discord.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
timeout: Optional[:class:`float`]
|
timeout: Optional[:class:`float`]
|
||||||
Timeout from last interaction with the UI before no longer accepting input.
|
Timeout in seconds from last interaction with the UI before no longer accepting input.
|
||||||
If ``None`` then there is no timeout.
|
If ``None`` then there is no timeout.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
@@ -144,7 +152,7 @@ class View:
|
|||||||
|
|
||||||
cls.__view_children_items__ = children
|
cls.__view_children_items__ = children
|
||||||
|
|
||||||
def __init__(self, timeout: Optional[float] = 180.0):
|
def __init__(self, *, timeout: Optional[float] = 180.0):
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.children: List[Item] = []
|
self.children: List[Item] = []
|
||||||
for func in self.__view_children_items__:
|
for func in self.__view_children_items__:
|
||||||
@@ -156,14 +164,32 @@ class View:
|
|||||||
|
|
||||||
self.__weights = _ViewWeights(self.children)
|
self.__weights = _ViewWeights(self.children)
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
self.id = os.urandom(16).hex()
|
self.id: str = os.urandom(16).hex()
|
||||||
self._cancel_callback: Optional[Callable[[View], None]] = None
|
self.__cancel_callback: Optional[Callable[[View], None]] = None
|
||||||
self._timeout_handler: Optional[asyncio.TimerHandle] = None
|
self.__timeout_expiry: Optional[float] = None
|
||||||
self._stopped = loop.create_future()
|
self.__timeout_task: Optional[asyncio.Task[None]] = None
|
||||||
|
self.__stopped: asyncio.Future[bool] = loop.create_future()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<{self.__class__.__name__} timeout={self.timeout} children={len(self.children)}>'
|
return f'<{self.__class__.__name__} timeout={self.timeout} children={len(self.children)}>'
|
||||||
|
|
||||||
|
async def __timeout_task_impl(self) -> None:
|
||||||
|
while True:
|
||||||
|
# Guard just in case someone changes the value of the timeout at runtime
|
||||||
|
if self.timeout is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.__timeout_expiry is None:
|
||||||
|
return self._dispatch_timeout()
|
||||||
|
|
||||||
|
# Check if we've elapsed our currently set timeout
|
||||||
|
now = time.monotonic()
|
||||||
|
if now >= self.__timeout_expiry:
|
||||||
|
return self._dispatch_timeout()
|
||||||
|
|
||||||
|
# Wait N seconds to see if timeout data has been refreshed
|
||||||
|
await asyncio.sleep(self.__timeout_expiry - now)
|
||||||
|
|
||||||
def to_components(self) -> List[Dict[str, Any]]:
|
def to_components(self) -> List[Dict[str, Any]]:
|
||||||
def key(item: Item) -> int:
|
def key(item: Item) -> int:
|
||||||
return item._rendered_row or 0
|
return item._rendered_row or 0
|
||||||
@@ -184,6 +210,33 @@ class View:
|
|||||||
|
|
||||||
return components
|
return components
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_message(cls, message: Message, /, *, timeout: Optional[float] = 180.0) -> View:
|
||||||
|
"""Converts a message's components into a :class:`View`.
|
||||||
|
|
||||||
|
The :attr:`.Message.components` of a message are read-only
|
||||||
|
and separate types from those in the ``discord.ui`` namespace.
|
||||||
|
In order to modify and edit message components they must be
|
||||||
|
converted into a :class:`View` first.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
message: :class:`discord.Message`
|
||||||
|
The message with components to convert into a view.
|
||||||
|
timeout: Optional[:class:`float`]
|
||||||
|
The timeout of the converted view.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`View`
|
||||||
|
The converted view. This always returns a :class:`View` and not
|
||||||
|
one of its subclasses.
|
||||||
|
"""
|
||||||
|
view = View(timeout=timeout)
|
||||||
|
for component in _walk_all_components(message.components):
|
||||||
|
view.add_item(_component_to_item(component))
|
||||||
|
return view
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _expires_at(self) -> Optional[float]:
|
def _expires_at(self) -> Optional[float]:
|
||||||
if self.timeout:
|
if self.timeout:
|
||||||
@@ -252,9 +305,8 @@ class View:
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If an exception occurs within the body then the interaction
|
If an exception occurs within the body then the check
|
||||||
check then :meth:`on_error` is called and it is considered
|
is considered a failure and :meth:`on_error` is called.
|
||||||
a failure.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
@@ -295,8 +347,11 @@ class View:
|
|||||||
print(f'Ignoring exception in view {self} for item {item}:', file=sys.stderr)
|
print(f'Ignoring exception in view {self} for item {item}:', file=sys.stderr)
|
||||||
traceback.print_exception(error.__class__, error, error.__traceback__, file=sys.stderr)
|
traceback.print_exception(error.__class__, error, error.__traceback__, file=sys.stderr)
|
||||||
|
|
||||||
async def _scheduled_task(self, state: Any, item: Item, interaction: Interaction):
|
async def _scheduled_task(self, item: Item, interaction: Interaction):
|
||||||
try:
|
try:
|
||||||
|
if self.timeout:
|
||||||
|
self.__timeout_expiry = time.monotonic() + self.timeout
|
||||||
|
|
||||||
allow = await self.interaction_check(interaction)
|
allow = await self.interaction_check(interaction)
|
||||||
if not allow:
|
if not allow:
|
||||||
return
|
return
|
||||||
@@ -307,21 +362,28 @@ class View:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return await self.on_error(e, item, interaction)
|
return await self.on_error(e, item, interaction)
|
||||||
|
|
||||||
def _start_listening(self, store: ViewStore) -> None:
|
def _start_listening_from_store(self, store: ViewStore) -> None:
|
||||||
self._cancel_callback = partial(store.remove_view)
|
self.__cancel_callback = partial(store.remove_view)
|
||||||
if self.timeout:
|
if self.timeout:
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
self._timeout_handler = loop.call_later(self.timeout, self.dispatch_timeout)
|
if self.__timeout_task is not None:
|
||||||
|
self.__timeout_task.cancel()
|
||||||
|
|
||||||
def dispatch_timeout(self):
|
self.__timeout_expiry = time.monotonic() + self.timeout
|
||||||
if self._stopped.done():
|
self.__timeout_task = loop.create_task(self.__timeout_task_impl())
|
||||||
|
|
||||||
|
def _dispatch_timeout(self):
|
||||||
|
if self.__stopped.done():
|
||||||
return
|
return
|
||||||
|
|
||||||
self._stopped.set_result(True)
|
self.__stopped.set_result(True)
|
||||||
asyncio.create_task(self.on_timeout(), name=f'discord-ui-view-timeout-{self.id}')
|
asyncio.create_task(self.on_timeout(), name=f'discord-ui-view-timeout-{self.id}')
|
||||||
|
|
||||||
def dispatch(self, state: Any, item: Item, interaction: Interaction):
|
def _dispatch_item(self, item: Item, interaction: Interaction):
|
||||||
asyncio.create_task(self._scheduled_task(state, item, interaction), name=f'discord-ui-view-dispatch-{self.id}')
|
if self.__stopped.done():
|
||||||
|
return
|
||||||
|
|
||||||
|
asyncio.create_task(self._scheduled_task(item, interaction), name=f'discord-ui-view-dispatch-{self.id}')
|
||||||
|
|
||||||
def refresh(self, components: List[Component]):
|
def refresh(self, components: List[Component]):
|
||||||
# This is pretty hacky at the moment
|
# This is pretty hacky at the moment
|
||||||
@@ -349,23 +411,25 @@ class View:
|
|||||||
|
|
||||||
This operation cannot be undone.
|
This operation cannot be undone.
|
||||||
"""
|
"""
|
||||||
if not self._stopped.done():
|
if not self.__stopped.done():
|
||||||
self._stopped.set_result(False)
|
self.__stopped.set_result(False)
|
||||||
|
|
||||||
if self._timeout_handler:
|
self.__timeout_expiry = None
|
||||||
self._timeout_handler.cancel()
|
if self.__timeout_task is not None:
|
||||||
|
self.__timeout_task.cancel()
|
||||||
|
self.__timeout_task = None
|
||||||
|
|
||||||
if self._cancel_callback:
|
if self.__cancel_callback:
|
||||||
self._cancel_callback(self)
|
self.__cancel_callback(self)
|
||||||
self._cancel_callback = None
|
self.__cancel_callback = None
|
||||||
|
|
||||||
def is_finished(self) -> bool:
|
def is_finished(self) -> bool:
|
||||||
""":class:`bool`: Whether the view has finished interacting."""
|
""":class:`bool`: Whether the view has finished interacting."""
|
||||||
return self._stopped.done()
|
return self.__stopped.done()
|
||||||
|
|
||||||
def is_dispatching(self) -> bool:
|
def is_dispatching(self) -> bool:
|
||||||
""":class:`bool`: Whether the view has been added for dispatching purposes."""
|
""":class:`bool`: Whether the view has been added for dispatching purposes."""
|
||||||
return self._cancel_callback is not None
|
return self.__cancel_callback is not None
|
||||||
|
|
||||||
def is_persistent(self) -> bool:
|
def is_persistent(self) -> bool:
|
||||||
""":class:`bool`: Whether the view is set up as persistent.
|
""":class:`bool`: Whether the view is set up as persistent.
|
||||||
@@ -387,31 +451,32 @@ class View:
|
|||||||
If ``True``, then the view timed out. If ``False`` then
|
If ``True``, then the view timed out. If ``False`` then
|
||||||
the view finished normally.
|
the view finished normally.
|
||||||
"""
|
"""
|
||||||
return await self._stopped
|
return await self.__stopped
|
||||||
|
|
||||||
|
|
||||||
class ViewStore:
|
class ViewStore:
|
||||||
def __init__(self, state: ConnectionState):
|
def __init__(self, state: ConnectionState):
|
||||||
# (component_type, custom_id): (View, Item, Expiry)
|
# (component_type, message_id, custom_id): (View, Item)
|
||||||
self._views: Dict[Tuple[int, str], Tuple[View, Item, Optional[float]]] = {}
|
self._views: Dict[Tuple[int, Optional[int], str], Tuple[View, Item]] = {}
|
||||||
# message_id: View
|
# message_id: View
|
||||||
self._synced_message_views: Dict[int, View] = {}
|
self._synced_message_views: Dict[int, View] = {}
|
||||||
self._state: ConnectionState = state
|
self._state: ConnectionState = state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def persistent_views(self) -> Sequence[View]:
|
def persistent_views(self) -> Sequence[View]:
|
||||||
|
# fmt: off
|
||||||
views = {
|
views = {
|
||||||
view.id: view
|
view.id: view
|
||||||
for (_, (view, _, _)) in self._views.items()
|
for (_, (view, _)) in self._views.items()
|
||||||
if view.is_persistent()
|
if view.is_persistent()
|
||||||
}
|
}
|
||||||
|
# fmt: on
|
||||||
return list(views.values())
|
return list(views.values())
|
||||||
|
|
||||||
def __verify_integrity(self):
|
def __verify_integrity(self):
|
||||||
to_remove: List[Tuple[int, str]] = []
|
to_remove: List[Tuple[int, Optional[int], str]] = []
|
||||||
now = time.monotonic()
|
for (k, (view, _)) in self._views.items():
|
||||||
for (k, (_, _, expiry)) in self._views.items():
|
if view.is_finished():
|
||||||
if expiry is not None and now >= expiry:
|
|
||||||
to_remove.append(k)
|
to_remove.append(k)
|
||||||
|
|
||||||
for k in to_remove:
|
for k in to_remove:
|
||||||
@@ -420,11 +485,10 @@ class ViewStore:
|
|||||||
def add_view(self, view: View, message_id: Optional[int] = None):
|
def add_view(self, view: View, message_id: Optional[int] = None):
|
||||||
self.__verify_integrity()
|
self.__verify_integrity()
|
||||||
|
|
||||||
expiry = view._expires_at
|
view._start_listening_from_store(self)
|
||||||
view._start_listening(self)
|
|
||||||
for item in view.children:
|
for item in view.children:
|
||||||
if item.is_dispatchable():
|
if item.is_dispatchable():
|
||||||
self._views[(item.type.value, item.custom_id)] = (view, item, expiry) # type: ignore
|
self._views[(item.type.value, message_id, item.custom_id)] = (view, item) # type: ignore
|
||||||
|
|
||||||
if message_id is not None:
|
if message_id is not None:
|
||||||
self._synced_message_views[message_id] = view
|
self._synced_message_views[message_id] = view
|
||||||
@@ -441,15 +505,17 @@ class ViewStore:
|
|||||||
|
|
||||||
def dispatch(self, component_type: int, custom_id: str, interaction: Interaction):
|
def dispatch(self, component_type: int, custom_id: str, interaction: Interaction):
|
||||||
self.__verify_integrity()
|
self.__verify_integrity()
|
||||||
key = (component_type, custom_id)
|
message_id: Optional[int] = interaction.message and interaction.message.id
|
||||||
value = self._views.get(key)
|
key = (component_type, message_id, custom_id)
|
||||||
|
# Fallback to None message_id searches in case a persistent view
|
||||||
|
# was added without an associated message_id
|
||||||
|
value = self._views.get(key) or self._views.get((component_type, None, custom_id))
|
||||||
if value is None:
|
if value is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
view, item, _ = value
|
view, item = value
|
||||||
self._views[key] = (view, item, view._expires_at)
|
|
||||||
item.refresh_state(interaction)
|
item.refresh_state(interaction)
|
||||||
view.dispatch(self._state, item, interaction)
|
view._dispatch_item(item, interaction)
|
||||||
|
|
||||||
def is_message_tracked(self, message_id: int):
|
def is_message_tracked(self, message_id: int):
|
||||||
return message_id in self._synced_message_views
|
return message_id in self._synced_message_views
|
||||||
|
|||||||
232
discord/user.py
232
discord/user.py
@@ -22,24 +22,54 @@ 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 Optional, TYPE_CHECKING
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional, Type, TypeVar, TYPE_CHECKING
|
||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
from .flags import PublicUserFlags
|
|
||||||
from .utils import snowflake_time, _bytes_to_base64_data
|
|
||||||
from .enums import DefaultAvatar
|
|
||||||
from .colour import Colour
|
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
|
from .colour import Colour
|
||||||
|
from .enums import DefaultAvatar
|
||||||
|
from .flags import PublicUserFlags
|
||||||
|
from .utils import snowflake_time, _bytes_to_base64_data, MISSING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from .channel import DMChannel
|
||||||
|
from .guild import Guild
|
||||||
|
from .message import Message
|
||||||
|
from .state import ConnectionState
|
||||||
|
from .types.channel import DMChannel as DMChannelPayload
|
||||||
|
from .types.user import User as UserPayload
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'User',
|
'User',
|
||||||
'ClientUser',
|
'ClientUser',
|
||||||
)
|
)
|
||||||
|
|
||||||
_BaseUser = discord.abc.User
|
BU = TypeVar('BU', bound='BaseUser')
|
||||||
|
|
||||||
|
|
||||||
class BaseUser(_BaseUser):
|
class _UserTag:
|
||||||
__slots__ = ('name', 'id', 'discriminator', '_avatar', 'bot', 'system', '_public_flags', '_state')
|
__slots__ = ()
|
||||||
|
id: int
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUser(_UserTag):
|
||||||
|
__slots__ = (
|
||||||
|
'name',
|
||||||
|
'id',
|
||||||
|
'discriminator',
|
||||||
|
'_avatar',
|
||||||
|
'_banner',
|
||||||
|
'_accent_colour',
|
||||||
|
'bot',
|
||||||
|
'system',
|
||||||
|
'_public_flags',
|
||||||
|
'_state',
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
name: str
|
name: str
|
||||||
@@ -47,53 +77,65 @@ class BaseUser(_BaseUser):
|
|||||||
discriminator: str
|
discriminator: str
|
||||||
bot: bool
|
bot: bool
|
||||||
system: bool
|
system: bool
|
||||||
|
_state: ConnectionState
|
||||||
|
_avatar: Optional[str]
|
||||||
|
_banner: Optional[str]
|
||||||
|
_accent_colour: Optional[str]
|
||||||
|
_public_flags: int
|
||||||
|
|
||||||
def __init__(self, *, state, data):
|
def __init__(self, *, state: ConnectionState, data: UserPayload) -> None:
|
||||||
self._state = state
|
self._state = state
|
||||||
self._update(data)
|
self._update(data)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"<BaseUser id={self.id} name={self.name!r} discriminator={self.discriminator!r}"
|
f"<BaseUser id={self.id} name={self.name!r} discriminator={self.discriminator!r}"
|
||||||
f" bot={self.bot} system={self.system}>"
|
f" bot={self.bot} system={self.system}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return f'{self.name}#{self.discriminator}'
|
return f'{self.name}#{self.discriminator}'
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __int__(self) -> int:
|
||||||
return isinstance(other, _BaseUser) and other.id == self.id
|
return self.id
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return isinstance(other, _UserTag) and other.id == self.id
|
||||||
|
|
||||||
|
def __ne__(self, other: Any) -> bool:
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return self.id >> 22
|
return self.id >> 22
|
||||||
|
|
||||||
def _update(self, data):
|
def _update(self, data: UserPayload) -> None:
|
||||||
self.name = data['username']
|
self.name = data['username']
|
||||||
self.id = int(data['id'])
|
self.id = int(data['id'])
|
||||||
self.discriminator = data['discriminator']
|
self.discriminator = data['discriminator']
|
||||||
self._avatar = data['avatar']
|
self._avatar = data['avatar']
|
||||||
|
self._banner = data.get('banner', None)
|
||||||
|
self._accent_colour = data.get('accent_color', None)
|
||||||
self._public_flags = data.get('public_flags', 0)
|
self._public_flags = data.get('public_flags', 0)
|
||||||
self.bot = data.get('bot', False)
|
self.bot = data.get('bot', False)
|
||||||
self.system = data.get('system', False)
|
self.system = data.get('system', False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _copy(cls, user):
|
def _copy(cls: Type[BU], user: BU) -> BU:
|
||||||
self = cls.__new__(cls) # bypass __init__
|
self = cls.__new__(cls) # bypass __init__
|
||||||
|
|
||||||
self.name = user.name
|
self.name = user.name
|
||||||
self.id = user.id
|
self.id = user.id
|
||||||
self.discriminator = user.discriminator
|
self.discriminator = user.discriminator
|
||||||
self._avatar = user._avatar
|
self._avatar = user._avatar
|
||||||
|
self._banner = user._banner
|
||||||
|
self._accent_colour = user._accent_colour
|
||||||
self.bot = user.bot
|
self.bot = user.bot
|
||||||
self._state = user._state
|
self._state = user._state
|
||||||
self._public_flags = user._public_flags
|
self._public_flags = user._public_flags
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _to_minimal_user_json(self):
|
def _to_minimal_user_json(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'username': self.name,
|
'username': self.name,
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
@@ -103,28 +145,82 @@ class BaseUser(_BaseUser):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_flags(self):
|
def public_flags(self) -> PublicUserFlags:
|
||||||
""":class:`PublicUserFlags`: The publicly available flags the user has."""
|
""":class:`PublicUserFlags`: The publicly available flags the user has."""
|
||||||
return PublicUserFlags._from_value(self._public_flags)
|
return PublicUserFlags._from_value(self._public_flags)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def avatar(self):
|
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):
|
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
|
@property
|
||||||
def colour(self):
|
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
|
||||||
|
def banner(self) -> Optional[Asset]:
|
||||||
|
"""Optional[:class:`Asset`]: Returns the user's banner asset, if available.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This information is only available via :meth:`Client.fetch_user`.
|
||||||
|
"""
|
||||||
|
if self._banner is None:
|
||||||
|
return None
|
||||||
|
return Asset._from_user_banner(self._state, self.id, self._banner)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accent_colour(self) -> Optional[Colour]:
|
||||||
|
"""Optional[:class:`Colour`]: Returns the user's accent colour, if applicable.
|
||||||
|
|
||||||
|
There is an alias for this named :attr:`accent_color`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This information is only available via :meth:`Client.fetch_user`.
|
||||||
|
"""
|
||||||
|
if self._accent_colour is None:
|
||||||
|
return None
|
||||||
|
return Colour(self._accent_colour)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accent_color(self) -> Optional[Colour]:
|
||||||
|
"""Optional[:class:`Colour`]: Returns the user's accent color, if applicable.
|
||||||
|
|
||||||
|
There is an alias for this named :attr:`accent_colour`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This information is only available via :meth:`Client.fetch_user`.
|
||||||
|
"""
|
||||||
|
return self.accent_colour
|
||||||
|
|
||||||
|
@property
|
||||||
|
def colour(self) -> Colour:
|
||||||
""":class:`Colour`: A property that returns a colour denoting the rendered colour
|
""":class:`Colour`: A property that returns a colour denoting the rendered colour
|
||||||
for the user. This always returns :meth:`Colour.default`.
|
for the user. This always returns :meth:`Colour.default`.
|
||||||
|
|
||||||
@@ -133,7 +229,7 @@ class BaseUser(_BaseUser):
|
|||||||
return Colour.default()
|
return Colour.default()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color(self):
|
def color(self) -> Colour:
|
||||||
""":class:`Colour`: A property that returns a color denoting the rendered color
|
""":class:`Colour`: A property that returns a color denoting the rendered color
|
||||||
for the user. This always returns :meth:`Colour.default`.
|
for the user. This always returns :meth:`Colour.default`.
|
||||||
|
|
||||||
@@ -142,12 +238,12 @@ class BaseUser(_BaseUser):
|
|||||||
return self.colour
|
return self.colour
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mention(self):
|
def mention(self) -> str:
|
||||||
""":class:`str`: Returns a string that allows you to mention the given user."""
|
""":class:`str`: Returns a string that allows you to mention the given user."""
|
||||||
return f'<@{self.id}>'
|
return f'<@{self.id}>'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created_at(self):
|
def created_at(self) -> datetime:
|
||||||
""":class:`datetime.datetime`: Returns the user's creation time in UTC.
|
""":class:`datetime.datetime`: Returns the user's creation time in UTC.
|
||||||
|
|
||||||
This is when the user's Discord account was created.
|
This is when the user's Discord account was created.
|
||||||
@@ -155,7 +251,7 @@ class BaseUser(_BaseUser):
|
|||||||
return snowflake_time(self.id)
|
return snowflake_time(self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self) -> str:
|
||||||
""":class:`str`: Returns the user's display name.
|
""":class:`str`: Returns the user's display name.
|
||||||
|
|
||||||
For regular users this is just their username, but
|
For regular users this is just their username, but
|
||||||
@@ -164,7 +260,7 @@ class BaseUser(_BaseUser):
|
|||||||
"""
|
"""
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def mentioned_in(self, message):
|
def mentioned_in(self, message: Message) -> bool:
|
||||||
"""Checks if the user is mentioned in the specified message.
|
"""Checks if the user is mentioned in the specified message.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -228,18 +324,24 @@ class ClientUser(BaseUser):
|
|||||||
Specifies if the user has MFA turned on and working.
|
Specifies if the user has MFA turned on and working.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = BaseUser.__slots__ + ('locale', '_flags', 'verified', 'mfa_enabled', '__weakref__')
|
__slots__ = ('locale', '_flags', 'verified', 'mfa_enabled', '__weakref__')
|
||||||
|
|
||||||
def __init__(self, *, state, data):
|
if TYPE_CHECKING:
|
||||||
|
verified: bool
|
||||||
|
locale: Optional[str]
|
||||||
|
mfa_enabled: bool
|
||||||
|
_flags: int
|
||||||
|
|
||||||
|
def __init__(self, *, state: ConnectionState, data: UserPayload) -> None:
|
||||||
super().__init__(state=state, data=data)
|
super().__init__(state=state, data=data)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f'<ClientUser id={self.id} name={self.name!r} discriminator={self.discriminator!r}'
|
f'<ClientUser id={self.id} name={self.name!r} discriminator={self.discriminator!r}'
|
||||||
f' bot={self.bot} verified={self.verified} mfa_enabled={self.mfa_enabled}>'
|
f' bot={self.bot} verified={self.verified} mfa_enabled={self.mfa_enabled}>'
|
||||||
)
|
)
|
||||||
|
|
||||||
def _update(self, data):
|
def _update(self, data: UserPayload) -> None:
|
||||||
super()._update(data)
|
super()._update(data)
|
||||||
# There's actually an Optional[str] phone field as well but I won't use it
|
# There's actually an Optional[str] phone field as well but I won't use it
|
||||||
self.verified = data.get('verified', False)
|
self.verified = data.get('verified', False)
|
||||||
@@ -247,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 = None, avatar: Optional[bytes] = None) -> 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.
|
||||||
@@ -261,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`
|
||||||
@@ -275,13 +380,21 @@ 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] = {}
|
||||||
|
if username is not MISSING:
|
||||||
|
payload['username'] = username
|
||||||
|
|
||||||
if avatar is not None:
|
if avatar is not MISSING:
|
||||||
avatar = _bytes_to_base64_data(avatar)
|
payload['avatar'] = _bytes_to_base64_data(avatar)
|
||||||
|
|
||||||
data = await self._state.http.edit_profile(username=username, avatar=avatar)
|
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):
|
||||||
@@ -305,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`
|
||||||
@@ -319,17 +436,34 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
Specifies if the user is a system user (i.e. represents Discord officially).
|
Specifies if the user is a system user (i.e. represents Discord officially).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = BaseUser.__slots__ + ('__weakref__',)
|
__slots__ = ('_stored',)
|
||||||
|
|
||||||
def __repr__(self):
|
def __init__(self, *, state: ConnectionState, data: UserPayload) -> None:
|
||||||
|
super().__init__(state=state, data=data)
|
||||||
|
self._stored: bool = False
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
return f'<User id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot}>'
|
return f'<User id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot}>'
|
||||||
|
|
||||||
async def _get_channel(self):
|
def __del__(self) -> None:
|
||||||
|
try:
|
||||||
|
if self._stored:
|
||||||
|
self._state.deref_user(self.id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _copy(cls, user: User):
|
||||||
|
self = super()._copy(user)
|
||||||
|
self._stored = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def _get_channel(self) -> DMChannel:
|
||||||
ch = await self.create_dm()
|
ch = await self.create_dm()
|
||||||
return ch
|
return ch
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dm_channel(self):
|
def dm_channel(self) -> Optional[DMChannel]:
|
||||||
"""Optional[:class:`DMChannel`]: Returns the channel associated with this user if it exists.
|
"""Optional[:class:`DMChannel`]: Returns the channel associated with this user if it exists.
|
||||||
|
|
||||||
If this returns ``None``, you can create a DM channel by calling the
|
If this returns ``None``, you can create a DM channel by calling the
|
||||||
@@ -338,7 +472,7 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
return self._state._get_private_channel_by_user(self.id)
|
return self._state._get_private_channel_by_user(self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mutual_guilds(self):
|
def mutual_guilds(self) -> List[Guild]:
|
||||||
"""List[:class:`Guild`]: The guilds that the user shares with the client.
|
"""List[:class:`Guild`]: The guilds that the user shares with the client.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -349,7 +483,7 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
"""
|
"""
|
||||||
return [guild for guild in self._state._guilds.values() if guild.get_member(self.id)]
|
return [guild for guild in self._state._guilds.values() if guild.get_member(self.id)]
|
||||||
|
|
||||||
async def create_dm(self):
|
async def create_dm(self) -> DMChannel:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Creates a :class:`DMChannel` with this user.
|
Creates a :class:`DMChannel` with this user.
|
||||||
@@ -367,5 +501,5 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
state = self._state
|
state = self._state
|
||||||
data = await state.http.start_private_message(self.id)
|
data: DMChannelPayload = await state.http.start_private_message(self.id)
|
||||||
return state.add_dm_channel(data)
|
return state.add_dm_channel(data)
|
||||||
|
|||||||
125
discord/utils.py
125
discord/utils.py
@@ -63,6 +63,14 @@ import warnings
|
|||||||
|
|
||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
|
|
||||||
|
try:
|
||||||
|
import orjson
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
HAS_ORJSON = False
|
||||||
|
else:
|
||||||
|
HAS_ORJSON = True
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'oauth_url',
|
'oauth_url',
|
||||||
'snowflake_time',
|
'snowflake_time',
|
||||||
@@ -75,6 +83,7 @@ __all__ = (
|
|||||||
'escape_markdown',
|
'escape_markdown',
|
||||||
'escape_mentions',
|
'escape_mentions',
|
||||||
'as_chunks',
|
'as_chunks',
|
||||||
|
'format_dt',
|
||||||
)
|
)
|
||||||
|
|
||||||
DISCORD_EPOCH = 1420070400000
|
DISCORD_EPOCH = 1420070400000
|
||||||
@@ -111,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
|
||||||
@@ -120,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
|
||||||
|
|
||||||
@@ -222,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
|
||||||
@@ -231,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."
|
||||||
@@ -251,18 +265,20 @@ def deprecated(instead: Optional[str] = None) -> Callable[[Callable[..., T]], Ca
|
|||||||
|
|
||||||
|
|
||||||
def oauth_url(
|
def oauth_url(
|
||||||
client_id: str,
|
client_id: Union[int, str],
|
||||||
permissions: Optional[Permissions] = None,
|
*,
|
||||||
guild: Optional[Snowflake] = None,
|
permissions: Permissions = MISSING,
|
||||||
redirect_uri: Optional[str] = None,
|
guild: Snowflake = MISSING,
|
||||||
scopes: Optional[Iterable[str]] = None,
|
redirect_uri: str = MISSING,
|
||||||
):
|
scopes: Iterable[str] = MISSING,
|
||||||
|
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.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
client_id: :class:`str`
|
client_id: Union[:class:`int`, :class:`str`]
|
||||||
The client ID for your bot.
|
The client ID for your bot.
|
||||||
permissions: :class:`~discord.Permissions`
|
permissions: :class:`~discord.Permissions`
|
||||||
The permissions you're requesting. If not given then you won't be requesting any
|
The permissions you're requesting. If not given then you won't be requesting any
|
||||||
@@ -275,6 +291,10 @@ def oauth_url(
|
|||||||
An optional valid list of scopes. Defaults to ``('bot',)``.
|
An optional valid list of scopes. Defaults to ``('bot',)``.
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
|
disable_guild_select: :class:`bool`
|
||||||
|
Whether to disallow the user from changing the guild dropdown.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
@@ -282,15 +302,17 @@ def oauth_url(
|
|||||||
The OAuth2 URL for inviting the bot into guilds.
|
The OAuth2 URL for inviting the bot into guilds.
|
||||||
"""
|
"""
|
||||||
url = f'https://discord.com/oauth2/authorize?client_id={client_id}'
|
url = f'https://discord.com/oauth2/authorize?client_id={client_id}'
|
||||||
url = url + '&scope=' + '+'.join(scopes or ('bot',))
|
url += '&scope=' + '+'.join(scopes or ('bot',))
|
||||||
if permissions is not None:
|
if permissions is not MISSING:
|
||||||
url = url + '&permissions=' + str(permissions.value)
|
url += f'&permissions={permissions.value}'
|
||||||
if guild is not None:
|
if guild is not MISSING:
|
||||||
url = url + "&guild_id=" + str(guild.id)
|
url += f'&guild_id={guild.id}'
|
||||||
if redirect_uri is not None:
|
if redirect_uri is not MISSING:
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
url = url + "&response_type=code&" + urlencode({'redirect_uri': redirect_uri})
|
url += '&response_type=code&' + urlencode({'redirect_uri': redirect_uri})
|
||||||
|
if disable_guild_select:
|
||||||
|
url += '&disable_guild_select=true'
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
@@ -307,7 +329,7 @@ def snowflake_time(id: int) -> datetime.datetime:
|
|||||||
An aware datetime in UTC representing the creation time of the snowflake.
|
An aware datetime in UTC representing the creation time of the snowflake.
|
||||||
"""
|
"""
|
||||||
timestamp = ((id >> 22) + DISCORD_EPOCH) / 1000
|
timestamp = ((id >> 22) + DISCORD_EPOCH) / 1000
|
||||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def time_snowflake(dt: datetime.datetime, high: bool = False) -> int:
|
def time_snowflake(dt: datetime.datetime, high: bool = False) -> int:
|
||||||
@@ -460,9 +482,20 @@ def _bytes_to_base64_data(data: bytes) -> str:
|
|||||||
return fmt.format(mime=mime, data=b64)
|
return fmt.format(mime=mime, data=b64)
|
||||||
|
|
||||||
|
|
||||||
def to_json(obj: Any) -> str:
|
if HAS_ORJSON:
|
||||||
|
|
||||||
|
def _to_json(obj: Any) -> str: # type: ignore
|
||||||
|
return orjson.dumps(obj).decode('utf-8')
|
||||||
|
|
||||||
|
_from_json = orjson.loads # type: ignore
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def _parse_ratelimit_header(request: Any, *, use_clock: bool = False) -> float:
|
def _parse_ratelimit_header(request: Any, *, use_clock: bool = False) -> float:
|
||||||
reset_after: Optional[str] = request.headers.get('X-Ratelimit-Reset-After')
|
reset_after: Optional[str] = request.headers.get('X-Ratelimit-Reset-After')
|
||||||
@@ -888,7 +921,7 @@ def evaluate_annotation(
|
|||||||
is_literal = False
|
is_literal = False
|
||||||
args = tp.__args__
|
args = tp.__args__
|
||||||
if not hasattr(tp, '__origin__'):
|
if not hasattr(tp, '__origin__'):
|
||||||
if PY_310 and tp.__class__ is types.Union:
|
if PY_310 and tp.__class__ is types.UnionType: # type: ignore
|
||||||
converted = Union[args] # type: ignore
|
converted = Union[args] # type: ignore
|
||||||
return evaluate_annotation(converted, globals, locals, cache)
|
return evaluate_annotation(converted, globals, locals, cache)
|
||||||
|
|
||||||
@@ -936,3 +969,51 @@ def resolve_annotation(
|
|||||||
if cache is None:
|
if cache is None:
|
||||||
cache = {}
|
cache = {}
|
||||||
return evaluate_annotation(annotation, globalns, locals, cache)
|
return evaluate_annotation(annotation, globalns, locals, cache)
|
||||||
|
|
||||||
|
|
||||||
|
TimestampStyle = Literal['f', 'F', 'd', 'D', 't', 'T', 'R']
|
||||||
|
|
||||||
|
|
||||||
|
def format_dt(dt: datetime.datetime, /, style: Optional[TimestampStyle] = None) -> str:
|
||||||
|
"""A helper function to format a :class:`datetime.datetime` for presentation within Discord.
|
||||||
|
|
||||||
|
This allows for a locale-independent way of presenting data using Discord specific Markdown.
|
||||||
|
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| Style | Example Output | Description |
|
||||||
|
+=============+============================+=================+
|
||||||
|
| t | 22:57 | Short Time |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| T | 22:57:58 | Long Time |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| d | 17/05/2016 | Short Date |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| D | 17 May 2016 | Long Date |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| f (default) | 17 May 2016 22:57 | Short Date Time |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| F | Tuesday, 17 May 2016 22:57 | Long Date Time |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
| R | 5 years ago | Relative Time |
|
||||||
|
+-------------+----------------------------+-----------------+
|
||||||
|
|
||||||
|
Note that the exact output depends on the user's locale setting in the client. The example output
|
||||||
|
presented is using the ``en-GB`` locale.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
dt: :class:`datetime.datetime`
|
||||||
|
The datetime to format.
|
||||||
|
style: :class:`str`
|
||||||
|
The style to format the datetime with.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`str`
|
||||||
|
The formatted string.
|
||||||
|
"""
|
||||||
|
if style is None:
|
||||||
|
return f'<t:{int(dt.timestamp())}>'
|
||||||
|
return f'<t:{int(dt.timestamp())}:{style}>'
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
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.
|
||||||
"""
|
|
||||||
|
|
||||||
"""Some documentation to refer to:
|
|
||||||
|
Some documentation to refer to:
|
||||||
|
|
||||||
- Our main web socket (mWS) sends opcode 4 with a guild ID and channel ID.
|
- Our main web socket (mWS) sends opcode 4 with a guild ID and channel ID.
|
||||||
- The mWS receives VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE.
|
- The mWS receives VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE.
|
||||||
@@ -37,21 +37,41 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
- Finally we can transmit data to endpoint:port.
|
- Finally we can transmit data to endpoint:port.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import socket
|
import socket
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
import threading
|
import threading
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, List, Optional, TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
from . import opus, utils
|
from . import opus, utils
|
||||||
from .backoff import ExponentialBackoff
|
from .backoff import ExponentialBackoff
|
||||||
from .gateway import *
|
from .gateway import *
|
||||||
from .errors import ClientException, ConnectionClosed
|
from .errors import ClientException, ConnectionClosed
|
||||||
from .player import AudioPlayer, AudioSource
|
from .player import AudioPlayer, AudioSource
|
||||||
|
from .utils import MISSING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .client import Client
|
||||||
|
from .guild import Guild
|
||||||
|
from .state import ConnectionState
|
||||||
|
from .user import ClientUser
|
||||||
|
from .opus import Encoder
|
||||||
|
from . import abc
|
||||||
|
|
||||||
|
from .types.voice import (
|
||||||
|
GuildVoiceState as GuildVoiceStatePayload,
|
||||||
|
VoiceServerUpdate as VoiceServerUpdatePayload,
|
||||||
|
SupportedModes,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
has_nacl: bool
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import nacl.secret
|
import nacl.secret # type: ignore
|
||||||
has_nacl = True
|
has_nacl = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
has_nacl = False
|
has_nacl = False
|
||||||
@@ -61,7 +81,10 @@ __all__ = (
|
|||||||
'VoiceClient',
|
'VoiceClient',
|
||||||
)
|
)
|
||||||
|
|
||||||
log = 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.
|
||||||
@@ -84,11 +107,11 @@ class VoiceProtocol:
|
|||||||
The voice channel that is being connected to.
|
The voice channel that is being connected to.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, client, channel):
|
def __init__(self, client: Client, channel: abc.Connectable) -> None:
|
||||||
self.client = client
|
self.client: Client = client
|
||||||
self.channel = channel
|
self.channel: abc.Connectable = channel
|
||||||
|
|
||||||
async def on_voice_state_update(self, data):
|
async def on_voice_state_update(self, data: GuildVoiceStatePayload) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
An abstract method that is called when the client's voice state
|
An abstract method that is called when the client's voice state
|
||||||
@@ -105,7 +128,7 @@ class VoiceProtocol:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def on_voice_server_update(self, data):
|
async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
An abstract method that is called when initially connecting to voice.
|
An abstract method that is called when initially connecting to voice.
|
||||||
@@ -122,7 +145,7 @@ class VoiceProtocol:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def connect(self, *, timeout: float, reconnect: bool):
|
async def connect(self, *, timeout: float, reconnect: bool) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
An abstract method called when the client initiates the connection request.
|
An abstract method called when the client initiates the connection request.
|
||||||
@@ -145,7 +168,7 @@ class VoiceProtocol:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def disconnect(self, *, force: bool):
|
async def disconnect(self, *, force: bool) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
An abstract method called when the client terminates the connection.
|
An abstract method called when the client terminates the connection.
|
||||||
@@ -159,7 +182,7 @@ class VoiceProtocol:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self) -> None:
|
||||||
"""This method *must* be called to ensure proper clean-up during a disconnect.
|
"""This method *must* be called to ensure proper clean-up during a disconnect.
|
||||||
|
|
||||||
It is advisable to call this from within :meth:`disconnect` when you are
|
It is advisable to call this from within :meth:`disconnect` when you are
|
||||||
@@ -198,48 +221,55 @@ class VoiceClient(VoiceProtocol):
|
|||||||
loop: :class:`asyncio.AbstractEventLoop`
|
loop: :class:`asyncio.AbstractEventLoop`
|
||||||
The event loop that the voice client is running on.
|
The event loop that the voice client is running on.
|
||||||
"""
|
"""
|
||||||
def __init__(self, client, channel):
|
endpoint_ip: str
|
||||||
|
voice_port: int
|
||||||
|
secret_key: List[int]
|
||||||
|
ssrc: int
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, client: Client, channel: abc.Connectable):
|
||||||
if not has_nacl:
|
if not has_nacl:
|
||||||
raise RuntimeError("PyNaCl library needed in order to use voice")
|
raise RuntimeError("PyNaCl library needed in order to use voice")
|
||||||
|
|
||||||
super().__init__(client, channel)
|
super().__init__(client, channel)
|
||||||
state = client._connection
|
state = client._connection
|
||||||
self.token = None
|
self.token: str = MISSING
|
||||||
self.socket = None
|
self.socket = MISSING
|
||||||
self.loop = state.loop
|
self.loop: asyncio.AbstractEventLoop = state.loop
|
||||||
self._state = state
|
self._state: ConnectionState = state
|
||||||
# this will be used in the AudioPlayer thread
|
# this will be used in the AudioPlayer thread
|
||||||
self._connected = threading.Event()
|
self._connected: threading.Event = threading.Event()
|
||||||
|
|
||||||
self._handshaking = False
|
self._handshaking: bool = False
|
||||||
self._potentially_reconnecting = False
|
self._potentially_reconnecting: bool = False
|
||||||
self._voice_state_complete = asyncio.Event()
|
self._voice_state_complete: asyncio.Event = asyncio.Event()
|
||||||
self._voice_server_complete = asyncio.Event()
|
self._voice_server_complete: asyncio.Event = asyncio.Event()
|
||||||
|
|
||||||
self.mode = None
|
self.mode: str = MISSING
|
||||||
self._connections = 0
|
self._connections: int = 0
|
||||||
self.sequence = 0
|
self.sequence: int = 0
|
||||||
self.timestamp = 0
|
self.timestamp: int = 0
|
||||||
self._runner = None
|
self.timeout: float = 0
|
||||||
self._player = None
|
self._runner: asyncio.Task = MISSING
|
||||||
self.encoder = None
|
self._player: Optional[AudioPlayer] = None
|
||||||
self._lite_nonce = 0
|
self.encoder: Encoder = MISSING
|
||||||
self.ws = None
|
self._lite_nonce: int = 0
|
||||||
|
self.ws: DiscordVoiceWebSocket = MISSING
|
||||||
|
|
||||||
warn_nacl = not has_nacl
|
warn_nacl = not has_nacl
|
||||||
supported_modes = (
|
supported_modes: Tuple[SupportedModes, ...] = (
|
||||||
'xsalsa20_poly1305_lite',
|
'xsalsa20_poly1305_lite',
|
||||||
'xsalsa20_poly1305_suffix',
|
'xsalsa20_poly1305_suffix',
|
||||||
'xsalsa20_poly1305',
|
'xsalsa20_poly1305',
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guild(self):
|
def guild(self) -> Optional[Guild]:
|
||||||
"""Optional[:class:`Guild`]: The guild we're connected to, if applicable."""
|
"""Optional[:class:`Guild`]: The guild we're connected to, if applicable."""
|
||||||
return getattr(self.channel, 'guild', None)
|
return getattr(self.channel, 'guild', None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self):
|
def user(self) -> ClientUser:
|
||||||
""":class:`ClientUser`: The user connected to voice (i.e. ourselves)."""
|
""":class:`ClientUser`: The user connected to voice (i.e. ourselves)."""
|
||||||
return self._state.user
|
return self._state.user
|
||||||
|
|
||||||
@@ -252,7 +282,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
|
|
||||||
# connection related
|
# connection related
|
||||||
|
|
||||||
async def on_voice_state_update(self, data):
|
async def on_voice_state_update(self, data: GuildVoiceStatePayload) -> None:
|
||||||
self.session_id = data['session_id']
|
self.session_id = data['session_id']
|
||||||
channel_id = data['channel_id']
|
channel_id = data['channel_id']
|
||||||
|
|
||||||
@@ -265,13 +295,13 @@ class VoiceClient(VoiceProtocol):
|
|||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
else:
|
else:
|
||||||
guild = self.guild
|
guild = self.guild
|
||||||
self.channel = channel_id and guild and guild.get_channel(int(channel_id))
|
self.channel = channel_id and guild and guild.get_channel(int(channel_id)) # type: ignore
|
||||||
else:
|
else:
|
||||||
self._voice_state_complete.set()
|
self._voice_state_complete.set()
|
||||||
|
|
||||||
async def on_voice_server_update(self, data):
|
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')
|
||||||
@@ -279,7 +309,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
|
||||||
|
|
||||||
@@ -289,7 +319,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
self.endpoint = self.endpoint[6:]
|
self.endpoint = self.endpoint[6:]
|
||||||
|
|
||||||
# This gets set later
|
# This gets set later
|
||||||
self.endpoint_ip = None
|
self.endpoint_ip = MISSING
|
||||||
|
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
self.socket.setblocking(False)
|
self.socket.setblocking(False)
|
||||||
@@ -301,27 +331,27 @@ class VoiceClient(VoiceProtocol):
|
|||||||
|
|
||||||
self._voice_server_complete.set()
|
self._voice_server_complete.set()
|
||||||
|
|
||||||
async def voice_connect(self):
|
async def voice_connect(self) -> None:
|
||||||
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):
|
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):
|
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):
|
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()
|
||||||
|
|
||||||
async def connect_websocket(self):
|
async def connect_websocket(self) -> DiscordVoiceWebSocket:
|
||||||
ws = await DiscordVoiceWebSocket.from_client(self)
|
ws = await DiscordVoiceWebSocket.from_client(self)
|
||||||
self._connected.clear()
|
self._connected.clear()
|
||||||
while ws.secret_key is None:
|
while ws.secret_key is None:
|
||||||
@@ -329,8 +359,8 @@ class VoiceClient(VoiceProtocol):
|
|||||||
self._connected.set()
|
self._connected.set()
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
async def connect(self, *, reconnect: bool, timeout: bool):
|
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):
|
||||||
@@ -358,17 +388,17 @@ 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
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if self._runner is None:
|
if self._runner is MISSING:
|
||||||
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
|
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
|
||||||
|
|
||||||
async def potential_reconnect(self):
|
async def potential_reconnect(self) -> bool:
|
||||||
# Attempt to stop the player thread from playing early
|
# Attempt to stop the player thread from playing early
|
||||||
self._connected.clear()
|
self._connected.clear()
|
||||||
self.prepare_handshake()
|
self.prepare_handshake()
|
||||||
@@ -391,7 +421,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latency(self):
|
def latency(self) -> float:
|
||||||
""":class:`float`: Latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
|
""":class:`float`: Latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
|
||||||
|
|
||||||
This could be referred to as the Discord Voice WebSocket latency and is
|
This could be referred to as the Discord Voice WebSocket latency and is
|
||||||
@@ -403,7 +433,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
return float("inf") if not ws else ws.latency
|
return float("inf") if not ws else ws.latency
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def average_latency(self):
|
def average_latency(self) -> float:
|
||||||
""":class:`float`: Average of most recent 20 HEARTBEAT latencies in seconds.
|
""":class:`float`: Average of most recent 20 HEARTBEAT latencies in seconds.
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
@@ -411,7 +441,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
ws = self.ws
|
ws = self.ws
|
||||||
return float("inf") if not ws else ws.average_latency
|
return float("inf") if not ws else ws.average_latency
|
||||||
|
|
||||||
async def poll_voice_ws(self, reconnect):
|
async def poll_voice_ws(self, reconnect: bool) -> None:
|
||||||
backoff = ExponentialBackoff()
|
backoff = ExponentialBackoff()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -423,14 +453,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:
|
||||||
@@ -441,7 +471,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()
|
||||||
@@ -449,10 +479,10 @@ 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):
|
async def disconnect(self, *, force: bool = False) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Disconnects this voice client from voice.
|
Disconnects this voice client from voice.
|
||||||
@@ -473,7 +503,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
if self.socket:
|
if self.socket:
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
|
|
||||||
async def move_to(self, channel):
|
async def move_to(self, channel: abc.Snowflake) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Moves you to a different voice channel.
|
Moves you to a different voice channel.
|
||||||
@@ -485,7 +515,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
"""
|
"""
|
||||||
await self.channel.guild.change_voice_state(channel=channel)
|
await self.channel.guild.change_voice_state(channel=channel)
|
||||||
|
|
||||||
def is_connected(self):
|
def is_connected(self) -> bool:
|
||||||
"""Indicates if the voice client is connected to voice."""
|
"""Indicates if the voice client is connected to voice."""
|
||||||
return self._connected.is_set()
|
return self._connected.is_set()
|
||||||
|
|
||||||
@@ -504,20 +534,20 @@ class VoiceClient(VoiceProtocol):
|
|||||||
encrypt_packet = getattr(self, '_encrypt_' + self.mode)
|
encrypt_packet = getattr(self, '_encrypt_' + self.mode)
|
||||||
return encrypt_packet(header, data)
|
return encrypt_packet(header, data)
|
||||||
|
|
||||||
def _encrypt_xsalsa20_poly1305(self, header, data):
|
def _encrypt_xsalsa20_poly1305(self, header: bytes, data) -> bytes:
|
||||||
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
||||||
nonce = bytearray(24)
|
nonce = bytearray(24)
|
||||||
nonce[:12] = header
|
nonce[:12] = header
|
||||||
|
|
||||||
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
|
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext
|
||||||
|
|
||||||
def _encrypt_xsalsa20_poly1305_suffix(self, header, data):
|
def _encrypt_xsalsa20_poly1305_suffix(self, header: bytes, data) -> bytes:
|
||||||
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
||||||
nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE)
|
nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE)
|
||||||
|
|
||||||
return header + box.encrypt(bytes(data), nonce).ciphertext + nonce
|
return header + box.encrypt(bytes(data), nonce).ciphertext + nonce
|
||||||
|
|
||||||
def _encrypt_xsalsa20_poly1305_lite(self, header, data):
|
def _encrypt_xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes:
|
||||||
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
box = nacl.secret.SecretBox(bytes(self.secret_key))
|
||||||
nonce = bytearray(24)
|
nonce = bytearray(24)
|
||||||
|
|
||||||
@@ -526,7 +556,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
|
|
||||||
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4]
|
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4]
|
||||||
|
|
||||||
def play(self, source: AudioSource, *, after: Callable[[Exception], Any]=None):
|
def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], Any]=None) -> None:
|
||||||
"""Plays an :class:`AudioSource`.
|
"""Plays an :class:`AudioSource`.
|
||||||
|
|
||||||
The finalizer, ``after`` is called after the source has been exhausted
|
The finalizer, ``after`` is called after the source has been exhausted
|
||||||
@@ -540,7 +570,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
-----------
|
-----------
|
||||||
source: :class:`AudioSource`
|
source: :class:`AudioSource`
|
||||||
The audio source we're reading from.
|
The audio source we're reading from.
|
||||||
after: Callable[[:class:`Exception`], Any]
|
after: Callable[[Optional[:class:`Exception`]], Any]
|
||||||
The finalizer that is called after the stream is exhausted.
|
The finalizer that is called after the stream is exhausted.
|
||||||
This function must have a single parameter, ``error``, that
|
This function must have a single parameter, ``error``, that
|
||||||
denotes an optional exception that was raised during playing.
|
denotes an optional exception that was raised during playing.
|
||||||
@@ -562,7 +592,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
raise ClientException('Already playing audio.')
|
raise ClientException('Already playing audio.')
|
||||||
|
|
||||||
if not isinstance(source, AudioSource):
|
if not isinstance(source, AudioSource):
|
||||||
raise TypeError(f'source must an AudioSource not {source.__class__.__name__}')
|
raise TypeError(f'source must be an AudioSource not {source.__class__.__name__}')
|
||||||
|
|
||||||
if not self.encoder and not source.is_opus():
|
if not self.encoder and not source.is_opus():
|
||||||
self.encoder = opus.Encoder()
|
self.encoder = opus.Encoder()
|
||||||
@@ -570,32 +600,32 @@ class VoiceClient(VoiceProtocol):
|
|||||||
self._player = AudioPlayer(source, self, after=after)
|
self._player = AudioPlayer(source, self, after=after)
|
||||||
self._player.start()
|
self._player.start()
|
||||||
|
|
||||||
def is_playing(self):
|
def is_playing(self) -> bool:
|
||||||
"""Indicates if we're currently playing audio."""
|
"""Indicates if we're currently playing audio."""
|
||||||
return self._player is not None and self._player.is_playing()
|
return self._player is not None and self._player.is_playing()
|
||||||
|
|
||||||
def is_paused(self):
|
def is_paused(self) -> bool:
|
||||||
"""Indicates if we're playing audio, but if we're paused."""
|
"""Indicates if we're playing audio, but if we're paused."""
|
||||||
return self._player is not None and self._player.is_paused()
|
return self._player is not None and self._player.is_paused()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
"""Stops playing audio."""
|
"""Stops playing audio."""
|
||||||
if self._player:
|
if self._player:
|
||||||
self._player.stop()
|
self._player.stop()
|
||||||
self._player = None
|
self._player = None
|
||||||
|
|
||||||
def pause(self):
|
def pause(self) -> None:
|
||||||
"""Pauses the audio playing."""
|
"""Pauses the audio playing."""
|
||||||
if self._player:
|
if self._player:
|
||||||
self._player.pause()
|
self._player.pause()
|
||||||
|
|
||||||
def resume(self):
|
def resume(self) -> None:
|
||||||
"""Resumes the audio playing."""
|
"""Resumes the audio playing."""
|
||||||
if self._player:
|
if self._player:
|
||||||
self._player.resume()
|
self._player.resume()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source(self):
|
def source(self) -> Optional[AudioSource]:
|
||||||
"""Optional[:class:`AudioSource`]: The audio source being played, if playing.
|
"""Optional[:class:`AudioSource`]: The audio source being played, if playing.
|
||||||
|
|
||||||
This property can also be used to change the audio source currently being played.
|
This property can also be used to change the audio source currently being played.
|
||||||
@@ -603,7 +633,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
return self._player.source if self._player else None
|
return self._player.source if self._player else None
|
||||||
|
|
||||||
@source.setter
|
@source.setter
|
||||||
def source(self, value):
|
def source(self, value: AudioSource) -> None:
|
||||||
if not isinstance(value, AudioSource):
|
if not isinstance(value, AudioSource):
|
||||||
raise TypeError(f'expected AudioSource not {value.__class__.__name__}.')
|
raise TypeError(f'expected AudioSource not {value.__class__.__name__}.')
|
||||||
|
|
||||||
@@ -612,7 +642,7 @@ class VoiceClient(VoiceProtocol):
|
|||||||
|
|
||||||
self._player._set_source(value)
|
self._player._set_source(value)
|
||||||
|
|
||||||
def send_audio_packet(self, data, *, encode=True):
|
def send_audio_packet(self, data: bytes, *, encode: bool = True) -> None:
|
||||||
"""Sends an audio packet composed of the data.
|
"""Sends an audio packet composed of the data.
|
||||||
|
|
||||||
You must be connected to play audio.
|
You must be connected to play audio.
|
||||||
@@ -641,6 +671,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)
|
||||||
|
|||||||
@@ -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,7 +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):
|
||||||
|
# state parameter is artificial
|
||||||
|
return BaseUser(state=self, data=data) # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def http(self):
|
def http(self):
|
||||||
@@ -636,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`]
|
||||||
@@ -654,9 +668,13 @@ class WebhookMessage(Message):
|
|||||||
This should not be mixed with the ``embeds`` parameter.
|
This should not be mixed with the ``embeds`` parameter.
|
||||||
file: :class:`File`
|
file: :class:`File`
|
||||||
The file to upload. This cannot be mixed with ``files`` parameter.
|
The file to upload. This cannot be mixed with ``files`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
files: List[:class:`File`]
|
files: List[:class:`File`]
|
||||||
A list of files to send with the content. This cannot be mixed with the
|
A list of files to send with the content. This cannot be mixed with the
|
||||||
``file`` parameter.
|
``file`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
allowed_mentions: :class:`AllowedMentions`
|
allowed_mentions: :class:`AllowedMentions`
|
||||||
Controls the mentions being processed in this message.
|
Controls the mentions being processed in this message.
|
||||||
See :meth:`.abc.Messageable.send` for more information.
|
See :meth:`.abc.Messageable.send` for more information.
|
||||||
@@ -664,6 +682,8 @@ class WebhookMessage(Message):
|
|||||||
The updated view to update this message with. If ``None`` is passed then
|
The updated view to update this message with. If ``None`` is passed then
|
||||||
the view is removed.
|
the view is removed.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@@ -676,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,
|
||||||
@@ -739,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):
|
||||||
@@ -756,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:
|
||||||
@@ -774,11 +797,16 @@ class BaseWebhook(Hashable):
|
|||||||
self.source_guild: Optional[PartialWebhookGuild] = source_guild
|
self.source_guild: Optional[PartialWebhookGuild] = source_guild
|
||||||
|
|
||||||
def is_partial(self) -> bool:
|
def is_partial(self) -> bool:
|
||||||
""":class:`bool`: Whether the webhook is a "partial" webhook."""
|
""":class:`bool`: Whether the webhook is a "partial" webhook.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0"""
|
||||||
return self.channel_id is None
|
return self.channel_id is None
|
||||||
|
|
||||||
def is_authenticated(self) -> bool:
|
def is_authenticated(self) -> bool:
|
||||||
""":class:`bool`: Whether the webhook is authenticated with a bot token."""
|
""":class:`bool`: Whether the webhook is authenticated with a bot token.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
return self.auth_token is not None
|
return self.auth_token is not None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -858,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.
|
||||||
|
|
||||||
@@ -895,7 +927,7 @@ class Webhook(BaseWebhook):
|
|||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__: Tuple[str, ...] = BaseWebhook.__slots__ + ('session',)
|
__slots__: Tuple[str, ...] = ('session',)
|
||||||
|
|
||||||
def __init__(self, data: WebhookPayload, session: aiohttp.ClientSession, token: Optional[str] = None, state=None):
|
def __init__(self, data: WebhookPayload, session: aiohttp.ClientSession, token: Optional[str] = None, state=None):
|
||||||
super().__init__(data, token, state)
|
super().__init__(data, token, state)
|
||||||
@@ -905,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
|
||||||
@@ -923,10 +955,14 @@ class Webhook(BaseWebhook):
|
|||||||
The session to use to send requests with. Note
|
The session to use to send requests with. Note
|
||||||
that the library does not manage the session and
|
that the library does not manage the session and
|
||||||
will not close it.
|
will not close it.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
bot_token: Optional[:class:`str`]
|
bot_token: Optional[:class:`str`]
|
||||||
The bot authentication token for authenticated requests
|
The bot authentication token for authenticated requests
|
||||||
involving the webhook.
|
involving the webhook.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
:class:`Webhook`
|
:class:`Webhook`
|
||||||
@@ -942,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
|
||||||
@@ -953,10 +989,14 @@ class Webhook(BaseWebhook):
|
|||||||
The session to use to send requests with. Note
|
The session to use to send requests with. Note
|
||||||
that the library does not manage the session and
|
that the library does not manage the session and
|
||||||
will not close it.
|
will not close it.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
bot_token: Optional[:class:`str`]
|
bot_token: Optional[:class:`str`]
|
||||||
The bot authentication token for authenticated requests
|
The bot authentication token for authenticated requests
|
||||||
involving the webhook.
|
involving the webhook.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
InvalidArgument
|
InvalidArgument
|
||||||
@@ -977,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'],
|
||||||
@@ -993,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)
|
||||||
|
|
||||||
@@ -1004,6 +1044,8 @@ class Webhook(BaseWebhook):
|
|||||||
|
|
||||||
This could be used to get a full webhook from a partial webhook.
|
This could be used to get a full webhook from a partial webhook.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
When fetching with an unauthenticated webhook, i.e.
|
When fetching with an unauthenticated webhook, i.e.
|
||||||
@@ -1056,6 +1098,8 @@ class Webhook(BaseWebhook):
|
|||||||
Whether to use the bot token over the webhook token
|
Whether to use the bot token over the webhook token
|
||||||
if available. Defaults to ``True``.
|
if available. Defaults to ``True``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@@ -1085,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.
|
||||||
@@ -1098,6 +1142,8 @@ class Webhook(BaseWebhook):
|
|||||||
A :term:`py:bytes-like object` representing the webhook's new default avatar.
|
A :term:`py:bytes-like object` representing the webhook's new default avatar.
|
||||||
channel: Optional[:class:`abc.Snowflake`]
|
channel: Optional[:class:`abc.Snowflake`]
|
||||||
The webhook's new channel. This requires an authenticated webhook.
|
The webhook's new channel. This requires an authenticated webhook.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
reason: Optional[:class:`str`]
|
reason: Optional[:class:`str`]
|
||||||
The reason for editing this webhook. Shows up on the audit log.
|
The reason for editing this webhook. Shows up on the audit log.
|
||||||
|
|
||||||
@@ -1106,6 +1152,8 @@ class Webhook(BaseWebhook):
|
|||||||
Whether to use the bot token over the webhook token
|
Whether to use the bot token over the webhook token
|
||||||
if available. Defaults to ``True``.
|
if available. Defaults to ``True``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@@ -1128,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:
|
||||||
@@ -1135,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
|
||||||
@@ -1158,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,
|
||||||
@@ -1178,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,
|
||||||
@@ -1197,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,
|
||||||
@@ -1234,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`
|
||||||
@@ -1244,6 +1297,8 @@ class Webhook(BaseWebhook):
|
|||||||
This is only available to :attr:`WebhookType.application` webhooks.
|
This is only available to :attr:`WebhookType.application` webhooks.
|
||||||
If a view is sent with an ephemeral message and it has no timeout set
|
If a view is sent with an ephemeral message and it has no timeout set
|
||||||
then the timeout is set to 15 minutes.
|
then the timeout is set to 15 minutes.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
file: :class:`File`
|
file: :class:`File`
|
||||||
The file to upload. This cannot be mixed with ``files`` parameter.
|
The file to upload. This cannot be mixed with ``files`` parameter.
|
||||||
files: List[:class:`File`]
|
files: List[:class:`File`]
|
||||||
@@ -1406,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.
|
||||||
@@ -1416,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`
|
||||||
@@ -1429,9 +1487,13 @@ class Webhook(BaseWebhook):
|
|||||||
This should not be mixed with the ``embeds`` parameter.
|
This should not be mixed with the ``embeds`` parameter.
|
||||||
file: :class:`File`
|
file: :class:`File`
|
||||||
The file to upload. This cannot be mixed with ``files`` parameter.
|
The file to upload. This cannot be mixed with ``files`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
files: List[:class:`File`]
|
files: List[:class:`File`]
|
||||||
A list of files to send with the content. This cannot be mixed with the
|
A list of files to send with the content. This cannot be mixed with the
|
||||||
``file`` parameter.
|
``file`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
allowed_mentions: :class:`AllowedMentions`
|
allowed_mentions: :class:`AllowedMentions`
|
||||||
Controls the mentions being processed in this message.
|
Controls the mentions being processed in this message.
|
||||||
See :meth:`.abc.Messageable.send` for more information.
|
See :meth:`.abc.Messageable.send` for more information.
|
||||||
@@ -1440,6 +1502,8 @@ class Webhook(BaseWebhook):
|
|||||||
the view is removed. The webhook must have state attached, similar to
|
the view is removed. The webhook must have state attached, similar to
|
||||||
:meth:`send`.
|
:meth:`send`.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
@@ -1453,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:
|
||||||
@@ -1476,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,
|
||||||
@@ -1486,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.
|
||||||
|
|||||||
@@ -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}'
|
||||||
@@ -147,8 +147,10 @@ class WebhookAdapter:
|
|||||||
file_data[name] = (p['filename'], p['value'], p['content_type'])
|
file_data[name] = (p['filename'], p['value'], p['content_type'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with session.request(method, url, data=to_send, files=file_data, headers=headers, params=params) as response:
|
with session.request(
|
||||||
log.debug(
|
method, url, data=to_send, files=file_data, headers=headers, params=params
|
||||||
|
) as response:
|
||||||
|
_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,
|
||||||
@@ -166,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)
|
||||||
@@ -179,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
|
||||||
|
|
||||||
@@ -346,8 +348,17 @@ class WebhookAdapter:
|
|||||||
return self.request(route, session=session)
|
return self.request(route, session=session)
|
||||||
|
|
||||||
|
|
||||||
_context = threading.local()
|
class _WebhookContext(threading.local):
|
||||||
|
adapter: Optional[WebhookAdapter] = None
|
||||||
|
|
||||||
|
|
||||||
|
_context = _WebhookContext()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_webhook_adapter() -> WebhookAdapter:
|
||||||
|
if _context.adapter is None:
|
||||||
_context.adapter = WebhookAdapter()
|
_context.adapter = WebhookAdapter()
|
||||||
|
return _context.adapter
|
||||||
|
|
||||||
|
|
||||||
class SyncWebhookMessage(Message):
|
class SyncWebhookMessage(Message):
|
||||||
@@ -362,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,
|
||||||
@@ -370,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
|
||||||
@@ -403,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,
|
||||||
@@ -457,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.
|
||||||
|
|
||||||
@@ -494,7 +516,7 @@ class SyncWebhook(BaseWebhook):
|
|||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__: Tuple[str, ...] = BaseWebhook.__slots__ + ('session',)
|
__slots__: Tuple[str, ...] = ('session',)
|
||||||
|
|
||||||
def __init__(self, data: WebhookPayload, session: Session, token: Optional[str] = None, state=None):
|
def __init__(self, data: WebhookPayload, session: Session, token: Optional[str] = None, state=None):
|
||||||
super().__init__(data, token, state)
|
super().__init__(data, token, state)
|
||||||
@@ -504,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
|
||||||
@@ -548,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
|
||||||
@@ -621,7 +643,7 @@ class SyncWebhook(BaseWebhook):
|
|||||||
:class:`SyncWebhook`
|
:class:`SyncWebhook`
|
||||||
The fetched webhook.
|
The fetched webhook.
|
||||||
"""
|
"""
|
||||||
adapter: WebhookAdapter = _context.adapter
|
adapter: WebhookAdapter = _get_webhook_adapter()
|
||||||
|
|
||||||
if prefer_auth and self.auth_token:
|
if prefer_auth and self.auth_token:
|
||||||
data = adapter.fetch_webhook(self.id, self.auth_token, session=self.session)
|
data = adapter.fetch_webhook(self.id, self.auth_token, session=self.session)
|
||||||
@@ -632,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
|
||||||
@@ -659,7 +681,7 @@ class SyncWebhook(BaseWebhook):
|
|||||||
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')
|
||||||
|
|
||||||
adapter: WebhookAdapter = _context.adapter
|
adapter: WebhookAdapter = _get_webhook_adapter()
|
||||||
|
|
||||||
if prefer_auth and self.auth_token:
|
if prefer_auth and self.auth_token:
|
||||||
adapter.delete_webhook(self.id, token=self.auth_token, session=self.session, reason=reason)
|
adapter.delete_webhook(self.id, token=self.auth_token, session=self.session, reason=reason)
|
||||||
@@ -674,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
|
||||||
@@ -702,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')
|
||||||
@@ -713,8 +740,9 @@ class SyncWebhook(BaseWebhook):
|
|||||||
if avatar is not MISSING:
|
if avatar is not MISSING:
|
||||||
payload['avatar'] = utils._bytes_to_base64_data(avatar) if avatar is not None else None
|
payload['avatar'] = utils._bytes_to_base64_data(avatar) if avatar is not None else None
|
||||||
|
|
||||||
adapter: WebhookAdapter = _context.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:
|
||||||
@@ -722,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(
|
||||||
@@ -743,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,
|
||||||
@@ -760,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,
|
||||||
@@ -776,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,
|
||||||
@@ -808,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`
|
||||||
@@ -873,7 +905,7 @@ class SyncWebhook(BaseWebhook):
|
|||||||
allowed_mentions=allowed_mentions,
|
allowed_mentions=allowed_mentions,
|
||||||
previous_allowed_mentions=previous_mentions,
|
previous_allowed_mentions=previous_mentions,
|
||||||
)
|
)
|
||||||
adapter: WebhookAdapter = _context.adapter
|
adapter: WebhookAdapter = _get_webhook_adapter()
|
||||||
thread_id: Optional[int] = None
|
thread_id: Optional[int] = None
|
||||||
if thread is not MISSING:
|
if thread is not MISSING:
|
||||||
thread_id = thread.id
|
thread_id = thread.id
|
||||||
@@ -891,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
|
||||||
@@ -921,7 +953,7 @@ class SyncWebhook(BaseWebhook):
|
|||||||
if self.token is None:
|
if self.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')
|
||||||
|
|
||||||
adapter: WebhookAdapter = _context.adapter
|
adapter: WebhookAdapter = _get_webhook_adapter()
|
||||||
data = adapter.get_webhook_message(
|
data = adapter.get_webhook_message(
|
||||||
self.id,
|
self.id,
|
||||||
self.token,
|
self.token,
|
||||||
@@ -940,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
|
||||||
@@ -995,8 +1027,8 @@ class SyncWebhook(BaseWebhook):
|
|||||||
allowed_mentions=allowed_mentions,
|
allowed_mentions=allowed_mentions,
|
||||||
previous_allowed_mentions=previous_mentions,
|
previous_allowed_mentions=previous_mentions,
|
||||||
)
|
)
|
||||||
adapter: WebhookAdapter = _context.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,
|
||||||
@@ -1005,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
|
||||||
@@ -1029,7 +1062,7 @@ class SyncWebhook(BaseWebhook):
|
|||||||
if self.token is None:
|
if self.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')
|
||||||
|
|
||||||
adapter: WebhookAdapter = _context.adapter
|
adapter: WebhookAdapter = _get_webhook_adapter()
|
||||||
adapter.delete_webhook_message(
|
adapter.delete_webhook_message(
|
||||||
self.id,
|
self.id,
|
||||||
self.token,
|
self.token,
|
||||||
|
|||||||
15
docs/_static/style.css
vendored
15
docs/_static/style.css
vendored
@@ -772,8 +772,7 @@ li > blockquote {
|
|||||||
|
|
||||||
/* admonitions */
|
/* admonitions */
|
||||||
div.admonition {
|
div.admonition {
|
||||||
padding: 0 0.8em;
|
padding: 0 0.8em 0.8em 0.8em !important;
|
||||||
padding-bottom: 0.8em;
|
|
||||||
margin: 0.8em 0;
|
margin: 0.8em 0;
|
||||||
border-radius: 2.5px;
|
border-radius: 2.5px;
|
||||||
border-left-width: 6px;
|
border-left-width: 6px;
|
||||||
@@ -783,7 +782,7 @@ div.admonition {
|
|||||||
|
|
||||||
p.admonition-title {
|
p.admonition-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0 -0.8rem;
|
margin: 0 -0.8rem !important;
|
||||||
padding: 0.4rem 0.6rem 0.4rem 2.5rem;
|
padding: 0.4rem 0.6rem 0.4rem 2.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
@@ -1041,12 +1040,18 @@ dl.function > dt,
|
|||||||
dl.attribute > dt,
|
dl.attribute > dt,
|
||||||
dl.classmethod > dt,
|
dl.classmethod > dt,
|
||||||
dl.method > dt,
|
dl.method > dt,
|
||||||
|
dl.property > dt,
|
||||||
dl.class > dt,
|
dl.class > dt,
|
||||||
dl.exception > dt {
|
dl.exception > dt {
|
||||||
background-color: var(--api-entry-background);
|
background-color: var(--api-entry-background);
|
||||||
padding: 1px 10px;
|
padding: 1px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* bug in sphinx: https://github.com/sphinx-doc/sphinx/issues/9384 */
|
||||||
|
dl.property > dt > span.descname + em.property {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
dd {
|
dd {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
@@ -1142,6 +1147,10 @@ table.docutils tbody tr td ol.last {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-default {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* hide the welcome text */
|
/* hide the welcome text */
|
||||||
section#welcome-to-discord-py > h1 {
|
section#welcome-to-discord-py > h1 {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
6
docs/_templates/layout.html
vendored
6
docs/_templates/layout.html
vendored
@@ -7,9 +7,6 @@
|
|||||||
{%- block extrahead %} {% endblock %}
|
{%- block extrahead %} {% endblock %}
|
||||||
<!-- end extra head -->
|
<!-- end extra head -->
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="{{ pathto('_static/style.css', 1)|e }}" type="text/css" />
|
|
||||||
<link rel="stylesheet" href="{{ pathto('_static/codeblocks.css', 1) }}" type="text/css" />
|
|
||||||
<link rel="stylesheet" href="{{ pathto('_static/icons.css', 1)|e }}" type="text/css" />
|
|
||||||
{%- block css %}
|
{%- block css %}
|
||||||
{%- for css in css_files %}
|
{%- for css in css_files %}
|
||||||
{%- if css|attr("filename") %}
|
{%- if css|attr("filename") %}
|
||||||
@@ -19,6 +16,9 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
<link rel="stylesheet" href="{{ pathto('_static/style.css', 1)|e }}" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="{{ pathto('_static/codeblocks.css', 1) }}" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="{{ pathto('_static/icons.css', 1)|e }}" type="text/css" />
|
||||||
{%- block scripts %}
|
{%- block scripts %}
|
||||||
<script id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
|
<script id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
|
||||||
{%- for js in script_files %}
|
{%- for js in script_files %}
|
||||||
|
|||||||
436
docs/api.rst
436
docs/api.rst
@@ -40,7 +40,10 @@ Client
|
|||||||
|
|
||||||
.. autoclass:: Client
|
.. autoclass:: Client
|
||||||
:members:
|
:members:
|
||||||
:exclude-members: fetch_guilds
|
:exclude-members: fetch_guilds, event
|
||||||
|
|
||||||
|
.. automethod:: Client.event()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. automethod:: Client.fetch_guilds
|
.. automethod:: Client.fetch_guilds
|
||||||
:async-for:
|
:async-for:
|
||||||
@@ -98,6 +101,7 @@ VoiceClient
|
|||||||
|
|
||||||
.. autoclass:: VoiceClient()
|
.. autoclass:: VoiceClient()
|
||||||
:members:
|
:members:
|
||||||
|
:exclude-members: connect, on_voice_state_update, on_voice_server_update
|
||||||
|
|
||||||
VoiceProtocol
|
VoiceProtocol
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
@@ -294,24 +298,36 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
:param kwargs: The keyword arguments for the event that raised the
|
:param kwargs: The keyword arguments for the event that raised the
|
||||||
exception.
|
exception.
|
||||||
|
|
||||||
|
.. function:: on_socket_event_type(event_type)
|
||||||
|
|
||||||
|
Called whenever a websocket event is received from the WebSocket.
|
||||||
|
|
||||||
|
This is mainly useful for logging how many events you are receiving
|
||||||
|
from the Discord gateway.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param event_type: The event type from Discord that is received, e.g. ``'READY'``.
|
||||||
|
:type event_type: :class:`str`
|
||||||
|
|
||||||
.. 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.
|
||||||
|
|
||||||
|
This requires setting the ``enable_debug_events`` setting in the :class:`Client`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This is only for the messages received from the client
|
This is only for the messages received from the client
|
||||||
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)
|
||||||
|
|
||||||
@@ -322,6 +338,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
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.
|
||||||
|
|
||||||
|
This requires setting the ``enable_debug_events`` setting in the :class:`Client`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This is only for the messages sent from the client
|
This is only for the messages sent from the client
|
||||||
@@ -597,7 +615,13 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
Called when an interaction happened.
|
Called when an interaction happened.
|
||||||
|
|
||||||
This currently happens due to slash command invocations.
|
This currently happens due to slash command invocations or components being used.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This is a low level function that is not generally meant to be used.
|
||||||
|
If you are working with components, consider using the callbacks associated
|
||||||
|
with the :class:`~discord.ui.View` instead as it provides a nicer user experience.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
@@ -660,7 +684,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
.. function:: on_thread_join(thread)
|
.. function:: on_thread_join(thread)
|
||||||
|
|
||||||
Called whenever a thread is joined.
|
Called whenever a thread is joined or created. Note that from the API's perspective there is no way to
|
||||||
|
differentiate between a thread being created or the bot joining a thread.
|
||||||
|
|
||||||
Note that you can get the guild from :attr:`Thread.guild`.
|
Note that you can get the guild from :attr:`Thread.guild`.
|
||||||
|
|
||||||
@@ -693,7 +718,11 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
.. function:: on_thread_delete(thread)
|
.. function:: on_thread_delete(thread)
|
||||||
|
|
||||||
Called whenever a thread is deleted.
|
Called whenever a thread is deleted. If the thread could
|
||||||
|
not be found in the internal cache this event will not be called.
|
||||||
|
Threads will not be in the cache if they are archived.
|
||||||
|
|
||||||
|
If you need this information use :func:`on_raw_thread_delete` instead.
|
||||||
|
|
||||||
Note that you can get the guild from :attr:`Thread.guild`.
|
Note that you can get the guild from :attr:`Thread.guild`.
|
||||||
|
|
||||||
@@ -704,6 +733,18 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
:param thread: The thread that got deleted.
|
:param thread: The thread that got deleted.
|
||||||
:type thread: :class:`Thread`
|
:type thread: :class:`Thread`
|
||||||
|
|
||||||
|
.. function:: on_raw_thread_delete(payload)
|
||||||
|
|
||||||
|
Called whenever a thread is deleted. Unlike :func:`on_thread_delete` this
|
||||||
|
is called regardless of the thread being in the internal thread cache or not.
|
||||||
|
|
||||||
|
This requires :attr:`Intents.guilds` to be enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param payload: The raw event payload data.
|
||||||
|
:type payload: :class:`RawThreadDeleteEvent`
|
||||||
|
|
||||||
.. function:: on_thread_member_join(member)
|
.. function:: on_thread_member_join(member)
|
||||||
on_thread_member_remove(member)
|
on_thread_member_remove(member)
|
||||||
|
|
||||||
@@ -713,6 +754,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
This requires :attr:`Intents.members` to be enabled.
|
This requires :attr:`Intents.members` to be enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
:param member: The member who joined or left.
|
:param member: The member who joined or left.
|
||||||
:type member: :class:`ThreadMember`
|
:type member: :class:`ThreadMember`
|
||||||
|
|
||||||
@@ -798,8 +841,6 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
This is called when one or more of the following things change:
|
This is called when one or more of the following things change:
|
||||||
|
|
||||||
- status
|
|
||||||
- activity
|
|
||||||
- nickname
|
- nickname
|
||||||
- roles
|
- roles
|
||||||
- pending
|
- pending
|
||||||
@@ -811,6 +852,24 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
:param after: The updated member's updated info.
|
:param after: The updated member's updated info.
|
||||||
:type after: :class:`Member`
|
:type after: :class:`Member`
|
||||||
|
|
||||||
|
.. function:: on_presence_update(before, after)
|
||||||
|
|
||||||
|
Called when a :class:`Member` updates their presence.
|
||||||
|
|
||||||
|
This is called when one or more of the following things change:
|
||||||
|
|
||||||
|
- status
|
||||||
|
- activity
|
||||||
|
|
||||||
|
This requires :attr:`Intents.presences` and :attr:`Intents.members` to be enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param before: The updated member's old info.
|
||||||
|
:type before: :class:`Member`
|
||||||
|
:param after: The updated member's updated info.
|
||||||
|
:type after: :class:`Member`
|
||||||
|
|
||||||
.. function:: on_user_update(before, after)
|
.. function:: on_user_update(before, after)
|
||||||
|
|
||||||
Called when a :class:`User` updates their profile.
|
Called when a :class:`User` updates their profile.
|
||||||
@@ -900,7 +959,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
|
|
||||||
Called when a :class:`Guild` adds or removes :class:`Emoji`.
|
Called when a :class:`Guild` adds or removes :class:`Emoji`.
|
||||||
|
|
||||||
This requires :attr:`Intents.emojis` to be enabled.
|
This requires :attr:`Intents.emojis_and_stickers` to be enabled.
|
||||||
|
|
||||||
:param guild: The guild who got their emojis updated.
|
:param guild: The guild who got their emojis updated.
|
||||||
:type guild: :class:`Guild`
|
:type guild: :class:`Guild`
|
||||||
@@ -909,6 +968,21 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
:param after: A list of emojis after the update.
|
:param after: A list of emojis after the update.
|
||||||
:type after: Sequence[:class:`Emoji`]
|
:type after: Sequence[:class:`Emoji`]
|
||||||
|
|
||||||
|
.. function:: on_guild_stickers_update(guild, before, after)
|
||||||
|
|
||||||
|
Called when a :class:`Guild` updates its stickers.
|
||||||
|
|
||||||
|
This requires :attr:`Intents.emojis_and_stickers` to be enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param guild: The guild who got their stickers updated.
|
||||||
|
:type guild: :class:`Guild`
|
||||||
|
:param before: A list of stickers before the update.
|
||||||
|
:type before: Sequence[:class:`GuildSticker`]
|
||||||
|
:param after: A list of stickers after the update.
|
||||||
|
:type after: Sequence[:class:`GuildSticker`]
|
||||||
|
|
||||||
.. function:: on_guild_available(guild)
|
.. function:: on_guild_available(guild)
|
||||||
on_guild_unavailable(guild)
|
on_guild_unavailable(guild)
|
||||||
|
|
||||||
@@ -1063,6 +1137,8 @@ Utility Functions
|
|||||||
|
|
||||||
.. autofunction:: discord.utils.utcnow
|
.. autofunction:: discord.utils.utcnow
|
||||||
|
|
||||||
|
.. autofunction:: discord.utils.format_dt
|
||||||
|
|
||||||
.. autofunction:: discord.utils.as_chunks
|
.. autofunction:: discord.utils.as_chunks
|
||||||
|
|
||||||
.. _discord-api-enums:
|
.. _discord-api-enums:
|
||||||
@@ -1125,7 +1201,7 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
A private thread
|
A private thread
|
||||||
|
|
||||||
.. versionadded:: 1.8
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
.. class:: MessageType
|
.. class:: MessageType
|
||||||
|
|
||||||
@@ -1290,7 +1366,7 @@ of :class:`enum.Enum`.
|
|||||||
The user is a system user (i.e. represents Discord officially).
|
The user is a system user (i.e. represents Discord officially).
|
||||||
.. attribute:: has_unread_urgent_messages
|
.. attribute:: has_unread_urgent_messages
|
||||||
|
|
||||||
The user has an unready system message.
|
The user has an unread system message.
|
||||||
.. attribute:: bug_hunter_level_2
|
.. attribute:: bug_hunter_level_2
|
||||||
|
|
||||||
The user is a Bug Hunter Level 2.
|
The user is a Bug Hunter Level 2.
|
||||||
@@ -1426,6 +1502,9 @@ of :class:`enum.Enum`.
|
|||||||
An alias for :attr:`primary`.
|
An alias for :attr:`primary`.
|
||||||
.. attribute:: grey
|
.. attribute:: grey
|
||||||
|
|
||||||
|
An alias for :attr:`secondary`.
|
||||||
|
.. attribute:: gray
|
||||||
|
|
||||||
An alias for :attr:`secondary`.
|
An alias for :attr:`secondary`.
|
||||||
.. attribute:: green
|
.. attribute:: green
|
||||||
|
|
||||||
@@ -1433,6 +1512,9 @@ of :class:`enum.Enum`.
|
|||||||
.. attribute:: red
|
.. attribute:: red
|
||||||
|
|
||||||
An alias for :attr:`danger`.
|
An alias for :attr:`danger`.
|
||||||
|
.. attribute:: url
|
||||||
|
|
||||||
|
An alias for :attr:`link`.
|
||||||
|
|
||||||
.. class:: VoiceRegion
|
.. class:: VoiceRegion
|
||||||
|
|
||||||
@@ -1524,6 +1606,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.
|
||||||
@@ -1566,6 +1650,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.
|
||||||
@@ -1581,6 +1688,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.
|
||||||
@@ -1711,6 +1820,7 @@ of :class:`enum.Enum`.
|
|||||||
- :attr:`~AuditLogDiff.bitrate`
|
- :attr:`~AuditLogDiff.bitrate`
|
||||||
- :attr:`~AuditLogDiff.rtc_region`
|
- :attr:`~AuditLogDiff.rtc_region`
|
||||||
- :attr:`~AuditLogDiff.video_quality_mode`
|
- :attr:`~AuditLogDiff.video_quality_mode`
|
||||||
|
- :attr:`~AuditLogDiff.default_auto_archive_duration`
|
||||||
|
|
||||||
.. attribute:: channel_delete
|
.. attribute:: channel_delete
|
||||||
|
|
||||||
@@ -1840,7 +1950,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.
|
||||||
@@ -2139,8 +2249,8 @@ of :class:`enum.Enum`.
|
|||||||
A stage instance was started.
|
A stage instance was started.
|
||||||
|
|
||||||
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
either :class:`Object` with the stage instance ID of the stage instance
|
the :class:`StageInstance` or :class:`Object` with the ID of the stage
|
||||||
which was created.
|
instance which was created.
|
||||||
|
|
||||||
Possible attributes for :class:`AuditLogDiff`:
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
@@ -2154,8 +2264,8 @@ of :class:`enum.Enum`.
|
|||||||
A stage instance was updated.
|
A stage instance was updated.
|
||||||
|
|
||||||
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
either :class:`Object` with the stage instance ID of the stage instance
|
the :class:`StageInstance` or :class:`Object` with the ID of the stage
|
||||||
which was updated.
|
instance which was updated.
|
||||||
|
|
||||||
Possible attributes for :class:`AuditLogDiff`:
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
@@ -2170,6 +2280,114 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: sticker_create
|
||||||
|
|
||||||
|
A sticker was created.
|
||||||
|
|
||||||
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
|
the :class:`GuildSticker` or :class:`Object` with the ID of the sticker
|
||||||
|
which was updated.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.emoji`
|
||||||
|
- :attr:`~AuditLogDiff.type`
|
||||||
|
- :attr:`~AuditLogDiff.format_type`
|
||||||
|
- :attr:`~AuditLogDiff.description`
|
||||||
|
- :attr:`~AuditLogDiff.available`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: sticker_update
|
||||||
|
|
||||||
|
A sticker was updated.
|
||||||
|
|
||||||
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
|
the :class:`GuildSticker` or :class:`Object` with the ID of the sticker
|
||||||
|
which was updated.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.emoji`
|
||||||
|
- :attr:`~AuditLogDiff.type`
|
||||||
|
- :attr:`~AuditLogDiff.format_type`
|
||||||
|
- :attr:`~AuditLogDiff.description`
|
||||||
|
- :attr:`~AuditLogDiff.available`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: sticker_delete
|
||||||
|
|
||||||
|
A sticker was deleted.
|
||||||
|
|
||||||
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
|
the :class:`GuildSticker` or :class:`Object` with the ID of the sticker
|
||||||
|
which was updated.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.emoji`
|
||||||
|
- :attr:`~AuditLogDiff.type`
|
||||||
|
- :attr:`~AuditLogDiff.format_type`
|
||||||
|
- :attr:`~AuditLogDiff.description`
|
||||||
|
- :attr:`~AuditLogDiff.available`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: thread_create
|
||||||
|
|
||||||
|
A thread was created.
|
||||||
|
|
||||||
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
|
the :class:`Thread` or :class:`Object` with the ID of the thread which
|
||||||
|
was created.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.archived`
|
||||||
|
- :attr:`~AuditLogDiff.locked`
|
||||||
|
- :attr:`~AuditLogDiff.auto_archive_duration`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: thread_update
|
||||||
|
|
||||||
|
A thread was updated.
|
||||||
|
|
||||||
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
|
the :class:`Thread` or :class:`Object` with the ID of the thread which
|
||||||
|
was updated.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.archived`
|
||||||
|
- :attr:`~AuditLogDiff.locked`
|
||||||
|
- :attr:`~AuditLogDiff.auto_archive_duration`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: thread_delete
|
||||||
|
|
||||||
|
A thread was deleted.
|
||||||
|
|
||||||
|
When this is the action, the type of :attr:`~AuditLogEntry.target` is
|
||||||
|
the :class:`Thread` or :class:`Object` with the ID of the thread which
|
||||||
|
was deleted.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.archived`
|
||||||
|
- :attr:`~AuditLogDiff.locked`
|
||||||
|
- :attr:`~AuditLogDiff.auto_archive_duration`
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
.. class:: AuditLogActionCategory
|
.. class:: AuditLogActionCategory
|
||||||
|
|
||||||
Represents the category that the :class:`AuditLogAction` belongs to.
|
Represents the category that the :class:`AuditLogAction` belongs to.
|
||||||
@@ -2233,7 +2451,7 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
.. attribute:: remove_role
|
.. attribute:: remove_role
|
||||||
|
|
||||||
This will remove the :attr:`Integration.role` from the user
|
This will remove the :attr:`StreamIntegration.role` from the user
|
||||||
when their subscription is finished.
|
when their subscription is finished.
|
||||||
|
|
||||||
.. attribute:: kick
|
.. attribute:: kick
|
||||||
@@ -2270,6 +2488,20 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
.. class:: StickerType
|
.. class:: StickerType
|
||||||
|
|
||||||
|
Represents the type of sticker.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: standard
|
||||||
|
|
||||||
|
Represents a standard sticker that all Nitro users can use.
|
||||||
|
|
||||||
|
.. attribute:: guild
|
||||||
|
|
||||||
|
Represents a custom sticker created in a guild.
|
||||||
|
|
||||||
|
.. class:: StickerFormatType
|
||||||
|
|
||||||
Represents the type of sticker images.
|
Represents the type of sticker images.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
@@ -2342,6 +2574,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.
|
||||||
@@ -2739,15 +2992,9 @@ AuditLogDiff
|
|||||||
|
|
||||||
.. attribute:: type
|
.. attribute:: type
|
||||||
|
|
||||||
The type of channel or channel permission overwrite.
|
The type of channel or sticker.
|
||||||
|
|
||||||
If the type is an :class:`int`, then it is a type of channel which can be either
|
:type: Union[:class:`ChannelType`, :class:`StickerType`]
|
||||||
``0`` to indicate a text channel or ``1`` to indicate a voice channel.
|
|
||||||
|
|
||||||
If the type is a :class:`str`, then it is a type of permission overwrite which
|
|
||||||
can be either ``'role'`` or ``'member'``.
|
|
||||||
|
|
||||||
:type: Union[:class:`int`, :class:`str`]
|
|
||||||
|
|
||||||
.. attribute:: topic
|
.. attribute:: topic
|
||||||
|
|
||||||
@@ -2954,6 +3201,64 @@ AuditLogDiff
|
|||||||
|
|
||||||
:type: :class:`VideoQualityMode`
|
:type: :class:`VideoQualityMode`
|
||||||
|
|
||||||
|
.. attribute:: format_type
|
||||||
|
|
||||||
|
The format type of a sticker being changed.
|
||||||
|
|
||||||
|
See also :attr:`GuildSticker.format`
|
||||||
|
|
||||||
|
:type: :class:`StickerFormatType`
|
||||||
|
|
||||||
|
.. attribute:: emoji
|
||||||
|
|
||||||
|
The name of the emoji that represents a sticker being changed.
|
||||||
|
|
||||||
|
See also :attr:`GuildSticker.emoji`
|
||||||
|
|
||||||
|
:type: :class:`str`
|
||||||
|
|
||||||
|
.. attribute:: description
|
||||||
|
|
||||||
|
The description of a sticker being changed.
|
||||||
|
|
||||||
|
See also :attr:`GuildSticker.description`
|
||||||
|
|
||||||
|
:type: :class:`str`
|
||||||
|
|
||||||
|
.. attribute:: available
|
||||||
|
|
||||||
|
The availability of a sticker being changed.
|
||||||
|
|
||||||
|
See also :attr:`GuildSticker.available`
|
||||||
|
|
||||||
|
:type: :class:`bool`
|
||||||
|
|
||||||
|
.. attribute:: archived
|
||||||
|
|
||||||
|
The thread is now archived.
|
||||||
|
|
||||||
|
:type: :class:`bool`
|
||||||
|
|
||||||
|
.. attribute:: locked
|
||||||
|
|
||||||
|
The thread is being locked or unlocked.
|
||||||
|
|
||||||
|
:type: :class:`bool`
|
||||||
|
|
||||||
|
.. attribute:: auto_archive_duration
|
||||||
|
|
||||||
|
The thread's auto archive duration being changed.
|
||||||
|
|
||||||
|
See also :attr:`Thread.auto_archive_duration`
|
||||||
|
|
||||||
|
:type: :class:`int`
|
||||||
|
|
||||||
|
.. attribute:: default_auto_archive_duration
|
||||||
|
|
||||||
|
The default auto archive duration for newly created threads being changed.
|
||||||
|
|
||||||
|
:type: :class:`int`
|
||||||
|
|
||||||
.. this is currently missing the following keys: reason and application_id
|
.. this is currently missing the following keys: reason and application_id
|
||||||
I'm not sure how to about porting these
|
I'm not sure how to about porting these
|
||||||
|
|
||||||
@@ -3260,6 +3565,14 @@ InteractionResponse
|
|||||||
.. autoclass:: InteractionResponse()
|
.. autoclass:: InteractionResponse()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
InteractionMessage
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: InteractionMessage
|
||||||
|
|
||||||
|
.. autoclass:: InteractionMessage()
|
||||||
|
:members:
|
||||||
|
|
||||||
Member
|
Member
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
@@ -3326,6 +3639,15 @@ RoleTags
|
|||||||
.. autoclass:: RoleTags()
|
.. autoclass:: RoleTags()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
PartialMessageable
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: PartialMessageable
|
||||||
|
|
||||||
|
.. autoclass:: PartialMessageable()
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
TextChannel
|
TextChannel
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -3500,6 +3822,22 @@ Widget
|
|||||||
.. autoclass:: Widget()
|
.. autoclass:: Widget()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
StickerPack
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: StickerPack
|
||||||
|
|
||||||
|
.. autoclass:: StickerPack()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
StickerItem
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: StickerItem
|
||||||
|
|
||||||
|
.. autoclass:: StickerItem()
|
||||||
|
:members:
|
||||||
|
|
||||||
Sticker
|
Sticker
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -3508,6 +3846,22 @@ Sticker
|
|||||||
.. autoclass:: Sticker()
|
.. autoclass:: Sticker()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
StandardSticker
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: StandardSticker
|
||||||
|
|
||||||
|
.. autoclass:: StandardSticker()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
GuildSticker
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: GuildSticker
|
||||||
|
|
||||||
|
.. autoclass:: GuildSticker()
|
||||||
|
:members:
|
||||||
|
|
||||||
RawMessageDeleteEvent
|
RawMessageDeleteEvent
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -3564,6 +3918,14 @@ RawIntegrationDeleteEvent
|
|||||||
.. autoclass:: RawIntegrationDeleteEvent()
|
.. autoclass:: RawIntegrationDeleteEvent()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
RawThreadDeleteEvent
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: RawThreadDeleteEvent
|
||||||
|
|
||||||
|
.. autoclass:: RawThreadDeleteEvent()
|
||||||
|
:members:
|
||||||
|
|
||||||
PartialWebhookGuild
|
PartialWebhookGuild
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -3593,7 +3955,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.
|
||||||
|
|
||||||
|
|
||||||
@@ -3807,6 +4169,17 @@ Button
|
|||||||
|
|
||||||
.. autofunction:: discord.ui.button
|
.. autofunction:: discord.ui.button
|
||||||
|
|
||||||
|
Select
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ui.Select
|
||||||
|
|
||||||
|
.. autoclass:: discord.ui.Select
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
|
.. autofunction:: discord.ui.select
|
||||||
|
|
||||||
|
|
||||||
Exceptions
|
Exceptions
|
||||||
------------
|
------------
|
||||||
@@ -3840,6 +4213,8 @@ The following exceptions are thrown by the library.
|
|||||||
|
|
||||||
.. autoexception:: PrivilegedIntentsRequired
|
.. autoexception:: PrivilegedIntentsRequired
|
||||||
|
|
||||||
|
.. autoexception:: InteractionResponded
|
||||||
|
|
||||||
.. autoexception:: discord.opus.OpusError
|
.. autoexception:: discord.opus.OpusError
|
||||||
|
|
||||||
.. autoexception:: discord.opus.OpusNotLoaded
|
.. autoexception:: discord.opus.OpusNotLoaded
|
||||||
@@ -3857,6 +4232,7 @@ Exception Hierarchy
|
|||||||
- :exc:`LoginFailure`
|
- :exc:`LoginFailure`
|
||||||
- :exc:`ConnectionClosed`
|
- :exc:`ConnectionClosed`
|
||||||
- :exc:`PrivilegedIntentsRequired`
|
- :exc:`PrivilegedIntentsRequired`
|
||||||
|
- :exc:`InteractionResponded`
|
||||||
- :exc:`NoMoreItems`
|
- :exc:`NoMoreItems`
|
||||||
- :exc:`GatewayNotFound`
|
- :exc:`GatewayNotFound`
|
||||||
- :exc:`HTTPException`
|
- :exc:`HTTPException`
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ extensions = [
|
|||||||
|
|
||||||
autodoc_member_order = 'bysource'
|
autodoc_member_order = 'bysource'
|
||||||
autodoc_typehints = 'none'
|
autodoc_typehints = 'none'
|
||||||
|
# maybe consider this?
|
||||||
|
# napoleon_attr_annotations = False
|
||||||
|
|
||||||
extlinks = {
|
extlinks = {
|
||||||
'issue': ('https://github.com/Rapptz/discord.py/issues/%s', 'GH-'),
|
'issue': ('https://github.com/Rapptz/discord.py/issues/%s', 'GH-'),
|
||||||
|
|||||||
@@ -18,6 +18,31 @@ Bot
|
|||||||
.. autoclass:: discord.ext.commands.Bot
|
.. autoclass:: discord.ext.commands.Bot
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
:exclude-members: after_invoke, before_invoke, check, check_once, command, event, group, listen
|
||||||
|
|
||||||
|
.. automethod:: Bot.after_invoke()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.before_invoke()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.check()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.check_once()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.command(*args, **kwargs)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.event()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.group(*args, **kwargs)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Bot.listen(name=None)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
AutoShardedBot
|
AutoShardedBot
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
@@ -84,8 +109,10 @@ Decorators
|
|||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.command
|
.. autofunction:: discord.ext.commands.command
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.group
|
.. autofunction:: discord.ext.commands.group
|
||||||
|
:decorator:
|
||||||
|
|
||||||
Command
|
Command
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
@@ -95,6 +122,16 @@ Command
|
|||||||
.. autoclass:: discord.ext.commands.Command
|
.. autoclass:: discord.ext.commands.Command
|
||||||
:members:
|
:members:
|
||||||
:special-members: __call__
|
:special-members: __call__
|
||||||
|
:exclude-members: after_invoke, before_invoke, error
|
||||||
|
|
||||||
|
.. automethod:: Command.after_invoke()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Command.before_invoke()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Command.error()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
Group
|
Group
|
||||||
~~~~~~
|
~~~~~~
|
||||||
@@ -104,6 +141,22 @@ Group
|
|||||||
.. autoclass:: discord.ext.commands.Group
|
.. autoclass:: discord.ext.commands.Group
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
:exclude-members: after_invoke, before_invoke, command, error, group
|
||||||
|
|
||||||
|
.. automethod:: Group.after_invoke()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Group.before_invoke()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Group.command(*args, **kwargs)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Group.error()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Group.group(*args, **kwargs)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
GroupMixin
|
GroupMixin
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
@@ -112,6 +165,13 @@ GroupMixin
|
|||||||
|
|
||||||
.. autoclass:: discord.ext.commands.GroupMixin
|
.. autoclass:: discord.ext.commands.GroupMixin
|
||||||
:members:
|
:members:
|
||||||
|
:exclude-members: command, group
|
||||||
|
|
||||||
|
.. automethod:: GroupMixin.command(*args, **kwargs)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: GroupMixin.group(*args, **kwargs)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. _ext_commands_api_cogs:
|
.. _ext_commands_api_cogs:
|
||||||
|
|
||||||
@@ -211,44 +271,73 @@ Enums
|
|||||||
Checks
|
Checks
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.check
|
.. autofunction:: discord.ext.commands.check(predicate)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.check_any
|
.. autofunction:: discord.ext.commands.check_any(*checks)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.has_role
|
.. autofunction:: discord.ext.commands.has_role(item)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.has_permissions
|
.. autofunction:: discord.ext.commands.has_permissions(**perms)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.has_guild_permissions
|
.. autofunction:: discord.ext.commands.has_guild_permissions(**perms)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.has_any_role
|
.. autofunction:: discord.ext.commands.has_any_role(*items)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.bot_has_role
|
.. autofunction:: discord.ext.commands.bot_has_role(item)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.bot_has_permissions
|
.. autofunction:: discord.ext.commands.bot_has_permissions(**perms)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.bot_has_guild_permissions
|
.. autofunction:: discord.ext.commands.bot_has_guild_permissions(**perms)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.bot_has_any_role
|
.. autofunction:: discord.ext.commands.bot_has_any_role(*items)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.cooldown
|
.. autofunction:: discord.ext.commands.cooldown(rate, per, type=discord.ext.commands.BucketType.default)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.max_concurrency
|
.. autofunction:: discord.ext.commands.dynamic_cooldown(cooldown, type=BucketType.default)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.before_invoke
|
.. autofunction:: discord.ext.commands.max_concurrency(number, per=discord.ext.commands.BucketType.default, *, wait=False)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.after_invoke
|
.. autofunction:: discord.ext.commands.before_invoke(coro)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.guild_only
|
.. autofunction:: discord.ext.commands.after_invoke(coro)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.dm_only
|
.. autofunction:: discord.ext.commands.guild_only(,)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.is_owner
|
.. autofunction:: discord.ext.commands.dm_only(,)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.is_nsfw
|
.. autofunction:: discord.ext.commands.is_owner(,)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. autofunction:: discord.ext.commands.is_nsfw(,)
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. _ext_commands_api_context:
|
.. _ext_commands_api_context:
|
||||||
|
|
||||||
|
Cooldown
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Cooldown
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.Cooldown
|
||||||
|
:members:
|
||||||
|
|
||||||
Context
|
Context
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -327,6 +416,12 @@ Converters
|
|||||||
.. autoclass:: discord.ext.commands.PartialEmojiConverter
|
.. autoclass:: discord.ext.commands.PartialEmojiConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.ThreadConverter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: discord.ext.commands.GuildStickerConverter
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.clean_content
|
.. autoclass:: discord.ext.commands.clean_content
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@@ -434,6 +529,9 @@ Exceptions
|
|||||||
.. autoexception:: discord.ext.commands.ChannelNotReadable
|
.. autoexception:: discord.ext.commands.ChannelNotReadable
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoexception:: discord.ext.commands.ThreadNotFound
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoexception:: discord.ext.commands.BadColourArgument
|
.. autoexception:: discord.ext.commands.BadColourArgument
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@@ -449,6 +547,9 @@ Exceptions
|
|||||||
.. autoexception:: discord.ext.commands.PartialEmojiConversionFailure
|
.. autoexception:: discord.ext.commands.PartialEmojiConversionFailure
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoexception:: discord.ext.commands.GuildStickerNotFound
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoexception:: discord.ext.commands.BadBoolArgument
|
.. autoexception:: discord.ext.commands.BadBoolArgument
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@@ -524,6 +625,7 @@ Exception Hierarchy
|
|||||||
- :exc:`~.commands.BadArgument`
|
- :exc:`~.commands.BadArgument`
|
||||||
- :exc:`~.commands.MessageNotFound`
|
- :exc:`~.commands.MessageNotFound`
|
||||||
- :exc:`~.commands.MemberNotFound`
|
- :exc:`~.commands.MemberNotFound`
|
||||||
|
- :exc:`~.commands.GuildNotFound`
|
||||||
- :exc:`~.commands.UserNotFound`
|
- :exc:`~.commands.UserNotFound`
|
||||||
- :exc:`~.commands.ChannelNotFound`
|
- :exc:`~.commands.ChannelNotFound`
|
||||||
- :exc:`~.commands.ChannelNotReadable`
|
- :exc:`~.commands.ChannelNotReadable`
|
||||||
@@ -531,8 +633,10 @@ Exception Hierarchy
|
|||||||
- :exc:`~.commands.RoleNotFound`
|
- :exc:`~.commands.RoleNotFound`
|
||||||
- :exc:`~.commands.BadInviteArgument`
|
- :exc:`~.commands.BadInviteArgument`
|
||||||
- :exc:`~.commands.EmojiNotFound`
|
- :exc:`~.commands.EmojiNotFound`
|
||||||
|
- :exc:`~.commands.GuildStickerNotFound`
|
||||||
- :exc:`~.commands.PartialEmojiConversionFailure`
|
- :exc:`~.commands.PartialEmojiConversionFailure`
|
||||||
- :exc:`~.commands.BadBoolArgument`
|
- :exc:`~.commands.BadBoolArgument`
|
||||||
|
- :exc:`~.commands.ThreadNotFound`
|
||||||
- :exc:`~.commands.FlagError`
|
- :exc:`~.commands.FlagError`
|
||||||
- :exc:`~.commands.BadFlagArgument`
|
- :exc:`~.commands.BadFlagArgument`
|
||||||
- :exc:`~.commands.MissingFlagArgument`
|
- :exc:`~.commands.MissingFlagArgument`
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Commands
|
Commands
|
||||||
==========
|
==========
|
||||||
|
|
||||||
One of the most appealing aspect of the command extension is how easy it is to define commands and
|
One of the most appealing aspects 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.
|
how you can arbitrarily nest groups and commands to have a rich sub-command system.
|
||||||
|
|
||||||
Commands are defined by attaching it to a regular Python function. The command is then invoked by the user using a similar
|
Commands are defined by attaching it to a regular Python function. The command is then invoked by the user using a similar
|
||||||
@@ -392,6 +392,7 @@ A lot of discord models work out of the gate as a parameter:
|
|||||||
- :class:`Colour`
|
- :class:`Colour`
|
||||||
- :class:`Emoji`
|
- :class:`Emoji`
|
||||||
- :class:`PartialEmoji`
|
- :class:`PartialEmoji`
|
||||||
|
- :class:`Thread` (since v2.0)
|
||||||
|
|
||||||
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you
|
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you
|
||||||
specify.
|
specify.
|
||||||
@@ -438,6 +439,8 @@ converter is given below:
|
|||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
| :class:`PartialEmoji` | :class:`~ext.commands.PartialEmojiConverter` |
|
| :class:`PartialEmoji` | :class:`~ext.commands.PartialEmojiConverter` |
|
||||||
+--------------------------+-------------------------------------------------+
|
+--------------------------+-------------------------------------------------+
|
||||||
|
| :class:`Thread` | :class:`~ext.commands.ThreadConverter` |
|
||||||
|
+--------------------------+-------------------------------------------------+
|
||||||
|
|
||||||
By providing the converter it allows us to use them as building blocks for another converter:
|
By providing the converter it allows us to use them as building blocks for another converter:
|
||||||
|
|
||||||
|
|||||||
@@ -140,5 +140,15 @@ API Reference
|
|||||||
.. autoclass:: discord.ext.tasks.Loop()
|
.. autoclass:: discord.ext.tasks.Loop()
|
||||||
:members:
|
:members:
|
||||||
:special-members: __call__
|
:special-members: __call__
|
||||||
|
:exclude-members: after_loop, before_loop, error
|
||||||
|
|
||||||
|
.. automethod:: Loop.after_loop()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Loop.before_loop()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
|
.. automethod:: Loop.error()
|
||||||
|
:decorator:
|
||||||
|
|
||||||
.. autofunction:: discord.ext.tasks.loop
|
.. autofunction:: discord.ext.tasks.loop
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ for Discord.
|
|||||||
|
|
||||||
- Modern Pythonic API using ``async``\/``await`` syntax
|
- Modern Pythonic API using ``async``\/``await`` syntax
|
||||||
- Sane rate limit handling that prevents 429s
|
- Sane rate limit handling that prevents 429s
|
||||||
- Implements the entire Discord API
|
|
||||||
- Command extension to aid with bot creation
|
- Command extension to aid with bot creation
|
||||||
- Easy to use with an object oriented design
|
- Easy to use with an object oriented design
|
||||||
- Optimised for both speed and memory
|
- Optimised for both speed and memory
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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` が使用できます。これはタプルのリストを返します -- 最初の要素がリスナーの名前で、二つ目の要素が関数そのものです。"
|
|
||||||
|
|
||||||
@@ -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 ""
|
|
||||||
|
|
||||||
@@ -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"
|
|
||||||
|
|
||||||
@@ -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`` にはこれを扱う拡張ライブラリがついてきます。"
|
|
||||||
|
|
||||||
@@ -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 ""
|
|
||||||
|
|
||||||
@@ -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`` のように使うことができます。"
|
|
||||||
|
|
||||||
@@ -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`"
|
|
||||||
|
|
||||||
@@ -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 ""
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user