From 5f2a9a2520437553e28e0bd48c872b2d49ebaf4a Mon Sep 17 00:00:00 2001 From: William Grigor Date: Wed, 1 Sep 2021 22:53:34 -0500 Subject: [PATCH 001/105] Added basic unit testing framework - Added middlewareplugin.py, this contains the proxy.py plugin that will intercept requests for you - Added tests.py, this contains the base test case and one "test" test --- .gitignore | 1 + tests/middlewareplugin.py | 20 ++++++++++ tests/tests.py | 80 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 tests/middlewareplugin.py create mode 100644 tests/tests.py diff --git a/.gitignore b/.gitignore index b556ebbb91..b87ab049c7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.py[cod] *.log *.egg-info +*.pem venv .venv docs/_build diff --git a/tests/middlewareplugin.py b/tests/middlewareplugin.py new file mode 100644 index 0000000000..700816f1ec --- /dev/null +++ b/tests/middlewareplugin.py @@ -0,0 +1,20 @@ +import logging +from typing import Optional + +from persistqueue import SQLiteAckQueue +from proxy.plugin import CacheResponsesPlugin +from proxy.http.exception import HttpRequestRejected +from proxy.http.parser import HttpParser + +class PyCordTestMiddleware(CacheResponsesPlugin): + + def handle_client_request(self, request: HttpParser) -> Optional[HttpParser]: + super().handle_client_request(request) + q = SQLiteAckQueue(path='testing.q') + allow = bool(int(request.header(b'X-Allow-Through'))) + q.put(request) + if allow: + return request + else: + raise HttpRequestRejected + diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000000..3657b3a63c --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,80 @@ +import shutil +from typing import Dict, Optional, Union +from unittest import IsolatedAsyncioTestCase, TestResult + +import aiohttp +import ssl +from aiohttp.client_reqrep import ClientResponse +from aiohttp.client_exceptions import ServerDisconnectedError +from persistqueue import SQLiteAckQueue +from proxy import Proxy + +class PyCordBaseTestCase(IsolatedAsyncioTestCase): + + def setUp(self) -> None: + """Called to set up environment before testing""" + super().setUp() + + def tearDown(self) -> None: + """Called to take down environment after testing""" + super().tearDown() + shutil.rmtree('testing.q') + + def run(self, result: Optional[TestResult] = None) -> None: + """Ensure the tests are run with a running proxy""" + with Proxy([ + '--num-workers', '1', + '--plugins', 'middlewareplugin.PyCordTestMiddleware', + '--ca-key-file', 'ca-key.pem', + '--ca-cert-file', 'ca-cert.pem', + '--ca-signing-key-file', 'ca-signing-key.pem', + '--port', '8899'] + ): + super().run(result) + + async def proxied_request(self, + url: str, + method: str, + allow_through_proxy: bool=False, + headers: Dict[str, str]={} + ) -> Optional[ClientResponse]: + """Send a request through the proxy to view request data + + This function will make a request to a URL through the testing proxy + which will dump the request out and prevent it from being sent to + discord's servers. This allows functionality to be tested without + any side-effects + + Parameters + ---------- + url : str + The URL to request + method : str + The request method (GET, POST, etc) + allow_through_proxy : bool + Should the request continue to the destination? (defaults to False) + headers : Optional[dict] + The headers to be sent with the request, defaults to an empty dict + Returns + ------- + Optional[ClientResponse] + If the request was allowed, the response is returned + """ + sslcontext = ssl.create_default_context(cafile='ca-cert.pem') + proxy = 'http://localhost:8899' + + try: + async with aiohttp.ClientSession(headers={'X-Allow-Through': str(int(allow_through_proxy))}|headers) as cs: + async with cs.request(method, url, proxy=proxy, ssl=sslcontext) as r: + return r + except ServerDisconnectedError: + return None + +class TestTest(PyCordBaseTestCase): + + async def test_proxy_sends(self) -> None: + q = SQLiteAckQueue('testing.q') + resp = await self.proxied_request('https://www.httpbin.org/get', 'GET', allow_through_proxy=True) + req = q.get() + q.ack(req) + self.assertEqual(resp.status, 200) From 4eb61f11459b3ebe70266b30698708388d1a0ae6 Mon Sep 17 00:00:00 2001 From: William Grigor Date: Fri, 8 Oct 2021 04:50:14 -0500 Subject: [PATCH 002/105] WIP unit testing support, NOT USABLE YET --- discord/http.py | 11 +++- tests/middlewareplugin.py | 12 ++-- tests/tests.py | 124 ++++++++++++++++++++++++++++---------- 3 files changed, 109 insertions(+), 38 deletions(-) diff --git a/discord/http.py b/discord/http.py index 04cc1355d9..4f74a8a74d 100644 --- a/discord/http.py +++ b/discord/http.py @@ -28,6 +28,7 @@ import asyncio import json import logging +import os import sys from typing import ( Any, @@ -56,6 +57,14 @@ _log = logging.getLogger(__name__) +TEST_MODE = bool(int(os.getenv('PYCORD_TEST_MODE'))) +if TEST_MODE: + import ssl + ssl_cert = os.getenv('PYCORD_CERT_PATH') + ssl_context_override = ssl.create_default_context(cafile=ssl_cert) +else: + ssl_context_override = None + if TYPE_CHECKING: from .file import File from .enums import ( @@ -378,7 +387,7 @@ async def static_login(self, token: str) -> user.User: self.token = token try: - data = await self.request(Route('GET', '/users/@me')) + data = await self.request(Route('GET', '/users/@me'), ssl=ssl_context_override) # Without this it can't login during unit testing except HTTPException as exc: self.token = old_token if exc.status == 401: diff --git a/tests/middlewareplugin.py b/tests/middlewareplugin.py index 700816f1ec..c7b0e65c65 100644 --- a/tests/middlewareplugin.py +++ b/tests/middlewareplugin.py @@ -1,20 +1,20 @@ -import logging from typing import Optional -from persistqueue import SQLiteAckQueue +from persistqueue import SQLiteQueue from proxy.plugin import CacheResponsesPlugin from proxy.http.exception import HttpRequestRejected from proxy.http.parser import HttpParser + class PyCordTestMiddleware(CacheResponsesPlugin): def handle_client_request(self, request: HttpParser) -> Optional[HttpParser]: super().handle_client_request(request) - q = SQLiteAckQueue(path='testing.q') - allow = bool(int(request.header(b'X-Allow-Through'))) + q = SQLiteQueue(path='testing.q') q.put(request) - if allow: + del q + header_present = 'X-Allow-Through' in request.headers.keys() + if not header_present or bool(int(request.header(b'X-Allow-Through'))): return request else: raise HttpRequestRejected - diff --git a/tests/tests.py b/tests/tests.py index 3657b3a63c..01fb75ecfc 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,43 +1,96 @@ +import asyncio +import os import shutil -from typing import Dict, Optional, Union +from typing import Any, Dict, Optional, Union, Tuple from unittest import IsolatedAsyncioTestCase, TestResult import aiohttp import ssl from aiohttp.client_reqrep import ClientResponse from aiohttp.client_exceptions import ServerDisconnectedError -from persistqueue import SQLiteAckQueue +from discord.http import HTTPClient, Route +from persistqueue import SQLiteQueue +from proxy.http.parser import HttpParser from proxy import Proxy +if os.name == 'nt': # SSL on windows breaks without this + policy = asyncio.WindowsSelectorEventLoopPolicy() + asyncio.set_event_loop_policy(policy) + + class PyCordBaseTestCase(IsolatedAsyncioTestCase): - def setUp(self) -> None: + async def asyncSetUp(self) -> None: """Called to set up environment before testing""" super().setUp() + self.proxy_url = "http://localhost:8899" + self.ssl = ssl.create_default_context(cafile='ca-cert.pem') + self.request_queue = SQLiteQueue('testing.q') + self.token = os.environ.get('PYCORD_BOT_TOKEN') + self.http_client = HTTPClient(proxy=self.proxy_url) + await self.http_client.static_login(self.token) - def tearDown(self) -> None: + async def asyncTearDown(self) -> None: """Called to take down environment after testing""" super().tearDown() + del self.request_queue shutil.rmtree('testing.q') + await self.http_client.close() def run(self, result: Optional[TestResult] = None) -> None: """Ensure the tests are run with a running proxy""" with Proxy([ - '--num-workers', '1', - '--plugins', 'middlewareplugin.PyCordTestMiddleware', - '--ca-key-file', 'ca-key.pem', - '--ca-cert-file', 'ca-cert.pem', - '--ca-signing-key-file', 'ca-signing-key.pem', - '--port', '8899'] - ): + '--num-workers', '1', + '--plugins', 'middlewareplugin.PyCordTestMiddleware', + '--ca-key-file', 'ca-key.pem', + '--ca-cert-file', 'ca-cert.pem', + '--ca-signing-key-file', 'ca-signing-key.pem', + '--port', '8899'] + ): super().run(result) + async def proxied_route_request(self, + route: Route, + allow_through_proxy: bool = False, + ) -> Tuple[HttpParser, Optional[Union[Dict[str, Any], str]]]: + """Send a request through the proxy to view request data + + This function will make a request to a URL through the testing proxy + which will dump the request out and prevent it from being sent to + discord's servers. This allows functionality to be tested without + any side-effects + + Parameters + ---------- + route : Route + The API endpoint to make the request to + allow_through_proxy : bool + Should the request continue to the destination? (defaults to False) + headers : Optional[dict] + The headers to be sent with the request, defaults to an empty dict + Returns + ------- + Tuple[HttpParser, Optional[Union[Dict[str, Any], str]]] + A tuple containing the request, and the response if it was + allowed through, otherwise None + """ + + try: + resp = await self.http_client.request(route, ssl=self.ssl, headers={ + 'X-Allow-Through': str(int(allow_through_proxy)) + }) + except ServerDisconnectedError: # Raised when the proxy blocks request + resp = None + finally: + req = self.request_queue.get() + return(req, resp) + async def proxied_request(self, - url: str, method: str, - allow_through_proxy: bool=False, - headers: Dict[str, str]={} - ) -> Optional[ClientResponse]: + url: str, + allow_through_proxy: bool = False, + headers: Dict[str, str] = {} + ) -> Tuple[HttpParser, Optional[ClientResponse]]: """Send a request through the proxy to view request data This function will make a request to a URL through the testing proxy @@ -47,34 +100,43 @@ async def proxied_request(self, Parameters ---------- - url : str - The URL to request method : str The request method (GET, POST, etc) + url : str + The URL to request allow_through_proxy : bool Should the request continue to the destination? (defaults to False) headers : Optional[dict] The headers to be sent with the request, defaults to an empty dict Returns ------- - Optional[ClientResponse] - If the request was allowed, the response is returned + Tuple[HttpParser, Optional[Union[Dict[str, Any], str]]] + A tuple containing the request, and the response if it was + allowed through, otherwise None """ - sslcontext = ssl.create_default_context(cafile='ca-cert.pem') - proxy = 'http://localhost:8899' try: - async with aiohttp.ClientSession(headers={'X-Allow-Through': str(int(allow_through_proxy))}|headers) as cs: - async with cs.request(method, url, proxy=proxy, ssl=sslcontext) as r: - return r - except ServerDisconnectedError: - return None + async with aiohttp.ClientSession() as cs: + async with cs.request( + method, + url, + proxy=self.proxy_url, + ssl=self.ssl, headers={ + 'X-Allow-Through': str(int(allow_through_proxy)) + } | headers) as r: + resp = r + except ServerDisconnectedError: # Raised when the proxy blocks request + resp = None + finally: + req = self.request_queue.get() + return (req, resp) + -class TestTest(PyCordBaseTestCase): +class TestPyCordTestingFixtures(PyCordBaseTestCase): async def test_proxy_sends(self) -> None: - q = SQLiteAckQueue('testing.q') - resp = await self.proxied_request('https://www.httpbin.org/get', 'GET', allow_through_proxy=True) - req = q.get() - q.ack(req) + _, resp = await self.proxied_request( + 'GET', + 'https://discord.com', + allow_through_proxy=True) self.assertEqual(resp.status, 200) From 2937439b80574fb6544163f58ee21074746f40e6 Mon Sep 17 00:00:00 2001 From: William Grigor Date: Wed, 3 Nov 2021 05:56:43 -0500 Subject: [PATCH 003/105] Improved unit testing framework - Added a makefile to help with cert generation and environment vars - Added a readme file - Created a custom request dataclass - Updated .gitignore to ignore env files and cert files - Made some changes to discord/http.py to facilitate functionality during testing - Fixed the request blocking system in proxy - Fixed the middleware dumping requests into queue as two different requests - Changed the middleware plugin to selectively dump only requests that are explicitely made through the `proxied_request` test method - Added logging to the middleware and test file --- .gitignore | 1 + discord/http.py | 3 +- tests/Makefile | 36 +++++++++++++++++++++ tests/README.md | 17 ++++++++++ tests/middlewareplugin.py | 48 +++++++++++++++++++++++---- tests/parsing.py | 32 ++++++++++++++++++ tests/requirements.txt | 6 ++++ tests/tests.py | 68 +++++++++++++++++++++++++++++++-------- 8 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 tests/Makefile create mode 100644 tests/README.md create mode 100644 tests/parsing.py create mode 100644 tests/requirements.txt diff --git a/.gitignore b/.gitignore index b87ab049c7..96ede5da67 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.log *.egg-info *.pem +.env venv .venv docs/_build diff --git a/discord/http.py b/discord/http.py index 4f74a8a74d..f74da0c5c1 100644 --- a/discord/http.py +++ b/discord/http.py @@ -57,10 +57,11 @@ _log = logging.getLogger(__name__) -TEST_MODE = bool(int(os.getenv('PYCORD_TEST_MODE'))) +TEST_MODE = bool(int(os.getenv('PYCORD_TEST_MODE', 0))) if TEST_MODE: import ssl ssl_cert = os.getenv('PYCORD_CERT_PATH') + assert os.path.exists(ssl_cert) ssl_context_override = ssl.create_default_context(cafile=ssl_cert) else: ssl_context_override = None diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000000..cab2b901c6 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,36 @@ +include .env +export PYCORD_TEST_MODE +export PYCORD_BOT_TOKEN +export PYCORD_CERT_PATH + +export PYTHONWARNINGS=ignore # Ignore useless proxy.py warnings + +CA_KEYFILE := pycord-key.pem +CA_CERTFILE := pycord-cert.pem +CA_SIGNKEY := pycord-signkey.pem + +envecho: + @echo $(PYCORD_TEST_MODE) + @echo $(PYCORD_BOT_TOKEN) + @echo $(PYCORD_CERT_PATH) + +runtests: + python -m unittest + +gencerts: + # Create CA keyfile and remove passphrase + python -m proxy.common.pki gen_private_key \ + --private-key-path $(CA_KEYFILE) + python -m proxy.common.pki remove_passphrase \ + --private-key-path $(CA_KEYFILE) + + # Create a CA certificate + python -m proxy.common.pki gen_public_key \ + --private-key-path $(CA_KEYFILE) \ + --public-key-path $(CA_CERTFILE) + + # Create CA signing key and remove passphrase + python -m proxy.common.pki gen_private_key \ + --private-key-path $(CA_SIGNKEY) + python -m proxy.common.pki remove_passphrase \ + --private-key-path $(CA_SIGNKEY) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..878fa6c261 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,17 @@ +# Unit Testing in PyCord +## Scope +Currently, only the internal framework can be unit tested, but plans are in place +for testing your applications +## Setup +First install all requirements from the requirements.txt file in this directory +### On GNU/Linux systems +The testing system uses GNU make as the interface, if you use the GNU coreutils, +make is likely already there, if not, consult your distro's documentation to get it + +In order to read the data of requests, you must generate some key files for the proxy, +you can do this by running `make gencerts` +### On Windows systems +A powershell script is in the works, but for now only a Makefile is available +## Running Tests +Execute the command `make runtests` to run the tests + diff --git a/tests/middlewareplugin.py b/tests/middlewareplugin.py index c7b0e65c65..57f93453bc 100644 --- a/tests/middlewareplugin.py +++ b/tests/middlewareplugin.py @@ -1,20 +1,54 @@ -from typing import Optional +import logging +from typing import Any, Dict, List, Optional from persistqueue import SQLiteQueue from proxy.plugin import CacheResponsesPlugin from proxy.http.exception import HttpRequestRejected from proxy.http.parser import HttpParser +from parsing import CombinedHttpRequest + +RequestTrackingDict = Dict[bytes, HttpParser] + class PyCordTestMiddleware(CacheResponsesPlugin): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.q = SQLiteQueue(path='testing.q') + self.request_tracker: RequestTrackingDict = {} + + def __del__(self): + del self.q + def handle_client_request(self, request: HttpParser) -> Optional[HttpParser]: super().handle_client_request(request) - q = SQLiteQueue(path='testing.q') - q.put(request) - del q - header_present = 'X-Allow-Through' in request.headers.keys() - if not header_present or bool(int(request.header(b'X-Allow-Through'))): + header_present = request.has_header(b'X-Allow-Through') + request_tracked = request.has_header(b'X-PyCord-Testing-ReqId') + if request_tracked: + request_id = request.header(b'X-PyCord-Testing-ReqId') + # Allow the initial CONNECT request go through and block the following one + if request.method == b'CONNECT': + logging.info( + f"Allowing CONNECT request to {request.host.decode('utf-8')!r} Tracking ID: {request_id.decode('utf-8') if request_tracked else None!r}") + # First time around, request is allowed and is added to tracked requests + if request_tracked: + self.request_tracker[request_id] = request return request else: - raise HttpRequestRejected + if request_tracked: + first_request = self.request_tracker.pop(request_id) + merged = CombinedHttpRequest.from_request_pair( + first_request, request) + self.q.put(merged) + + destination = request.url.netloc.decode( + 'utf-8') if request.url.netloc else request.url.path.decode('utf-8') + if not header_present or bool(int(request.header(b'X-Allow-Through'))): + logging.info( + f"Allowing request to {destination!r} Tracking ID: {request_id.decode('utf-8') if request_tracked else None!r}") + return request + else: + logging.info( + f"Rejecting request to {destination!r} Tracking ID: {request_id.decode('utf-8') if request_tracked else None!r}") + raise HttpRequestRejected diff --git a/tests/parsing.py b/tests/parsing.py new file mode 100644 index 0000000000..3c96453170 --- /dev/null +++ b/tests/parsing.py @@ -0,0 +1,32 @@ +import json +import logging +from dataclasses import dataclass +from typing import Any, Dict, Optional, Type, TypeVar, Union + +from proxy.http.parser import HttpParser + +T = TypeVar('T', bound='CombinedHttpRequest') + + +@dataclass +class CombinedHttpRequest: + headers: Dict[str, str] + host: str + path: str + body: Optional[Union[str, bytes, Dict[str, Any]]] + + @classmethod + def from_request_pair(cls: Type[T], connect_req: HttpParser, final_req: HttpParser) -> T: + headers_merged = connect_req.headers | final_req.headers + parsed_headers = {k.decode('utf-8'): v.decode('utf-8') + for k, v in headers_merged.values()} + host = connect_req.url.netloc.decode('utf-8').split(':')[0] + path = final_req.url.path.decode('utf-8') + content_type = parsed_headers['Content-Type'] + if content_type == 'text/plain': + body = final_req.body.decode('utf-8') + elif content_type == 'application/json': + body = json.loads(final_req.body) + else: + body = final_req.body + return cls(headers=parsed_headers, host=host, body=body, path=path) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000000..7e34658c39 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,6 @@ +aiounittest +persist-queue +proxy.py +filemagic +pyOpenSSL +ndg-httpsclient diff --git a/tests/tests.py b/tests/tests.py index 01fb75ecfc..c622a7c804 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,10 +1,14 @@ import asyncio +import logging import os +import random import shutil +import string from typing import Any, Dict, Optional, Union, Tuple from unittest import IsolatedAsyncioTestCase, TestResult import aiohttp +import magic import ssl from aiohttp.client_reqrep import ClientResponse from aiohttp.client_exceptions import ServerDisconnectedError @@ -13,6 +17,10 @@ from proxy.http.parser import HttpParser from proxy import Proxy +RANDOM_ID_CHARS = string.ascii_letters + string.digits + +logging.getLogger('proxy').setLevel(logging.WARNING) + if os.name == 'nt': # SSL on windows breaks without this policy = asyncio.WindowsSelectorEventLoopPolicy() asyncio.set_event_loop_policy(policy) @@ -24,12 +32,26 @@ async def asyncSetUp(self) -> None: """Called to set up environment before testing""" super().setUp() self.proxy_url = "http://localhost:8899" - self.ssl = ssl.create_default_context(cafile='ca-cert.pem') + self.ssl = ssl.create_default_context( + cafile=os.getenv('PYCORD_CERT_PATH')) self.request_queue = SQLiteQueue('testing.q') - self.token = os.environ.get('PYCORD_BOT_TOKEN') + self.token = os.getenv('PYCORD_BOT_TOKEN') self.http_client = HTTPClient(proxy=self.proxy_url) + self.request_ids = set() await self.http_client.static_login(self.token) + def _generate_request_id(self) -> str: + new_id = ''.join(random.sample(RANDOM_ID_CHARS, 4)) + # Ensure that a random ID doesnt clash + if new_id in self.request_ids: + return self._generate_request_id() + else: + self.request_ids.add(new_id) + return new_id + + def _tracking_header(self) -> Dict[str, str]: + return {'X-PyCord-Testing-ReqId': self._generate_request_id()} + async def asyncTearDown(self) -> None: """Called to take down environment after testing""" super().tearDown() @@ -42,11 +64,11 @@ def run(self, result: Optional[TestResult] = None) -> None: with Proxy([ '--num-workers', '1', '--plugins', 'middlewareplugin.PyCordTestMiddleware', - '--ca-key-file', 'ca-key.pem', - '--ca-cert-file', 'ca-cert.pem', - '--ca-signing-key-file', 'ca-signing-key.pem', + '--ca-key-file', 'pycord-key.pem', + '--ca-cert-file', 'pycord-cert.pem', + '--ca-signing-key-file', 'pycord-signkey.pem', '--port', '8899'] - ): + ): super().run(result) async def proxied_route_request(self, @@ -89,7 +111,8 @@ async def proxied_request(self, method: str, url: str, allow_through_proxy: bool = False, - headers: Dict[str, str] = {} + headers: Dict[str, str] = {}, + body: Union[str, bytes, Dict[str, Any]] = '' ) -> Tuple[HttpParser, Optional[ClientResponse]]: """Send a request through the proxy to view request data @@ -108,22 +131,37 @@ async def proxied_request(self, Should the request continue to the destination? (defaults to False) headers : Optional[dict] The headers to be sent with the request, defaults to an empty dict + body : Union[str, bytes, Dict[str, Any]] + The body of the request, can either be + a string, json data, or raw bytes Returns ------- Tuple[HttpParser, Optional[Union[Dict[str, Any], str]]] A tuple containing the request, and the response if it was allowed through, otherwise None """ - try: - async with aiohttp.ClientSession() as cs: + with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m: + content_type = m.id_buffer(body) + async with aiohttp.ClientSession(headers={ + 'Connection': 'close', + 'Content-Type': content_type, + 'X-Allow-Through': str(int(allow_through_proxy)) + } | self._tracking_header() | headers) as cs: + if type(body) is dict: + json = body + data = None + else: + json = None + data = body + async with cs.request( method, url, proxy=self.proxy_url, - ssl=self.ssl, headers={ - 'X-Allow-Through': str(int(allow_through_proxy)) - } | headers) as r: + data=data, + json=json, + ssl=self.ssl) as r: resp = r except ServerDisconnectedError: # Raised when the proxy blocks request resp = None @@ -137,6 +175,10 @@ class TestPyCordTestingFixtures(PyCordBaseTestCase): async def test_proxy_sends(self) -> None: _, resp = await self.proxied_request( 'GET', - 'https://discord.com', + 'https://httpbin.org/get', allow_through_proxy=True) self.assertEqual(resp.status, 200) + + async def test_proxy_blocks(self) -> None: + _, resp = await self.proxied_request('GET', 'https://httpbin.org/get', allow_through_proxy=False) + self.assertIsNone(resp) From a9d7543b0fc05ad4ea3cfdbb37e33a4551d9f418 Mon Sep 17 00:00:00 2001 From: William Grigor Date: Sat, 6 Nov 2021 02:51:26 -0500 Subject: [PATCH 004/105] General code cleanup Fixed a spelling error Corrected types from HttpParser to the new CombinedHttpRequest Added method docstrings Added comments and ignore some faulty type checker errors --- tests/tests.py | 65 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index c622a7c804..f58bed1d95 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -14,9 +14,10 @@ from aiohttp.client_exceptions import ServerDisconnectedError from discord.http import HTTPClient, Route from persistqueue import SQLiteQueue -from proxy.http.parser import HttpParser from proxy import Proxy +from parsing import CombinedHttpRequest + RANDOM_ID_CHARS = string.ascii_letters + string.digits logging.getLogger('proxy').setLevel(logging.WARNING) @@ -35,14 +36,22 @@ async def asyncSetUp(self) -> None: self.ssl = ssl.create_default_context( cafile=os.getenv('PYCORD_CERT_PATH')) self.request_queue = SQLiteQueue('testing.q') - self.token = os.getenv('PYCORD_BOT_TOKEN') + # Setting default value allows pycord to raise LoginFailure + self.token = os.getenv('PYCORD_BOT_TOKEN', '') self.http_client = HTTPClient(proxy=self.proxy_url) self.request_ids = set() await self.http_client.static_login(self.token) def _generate_request_id(self) -> str: + """Generates a unique request ID + + Returns + ------- + str + A four digit unique request ID + """ new_id = ''.join(random.sample(RANDOM_ID_CHARS, 4)) - # Ensure that a random ID doesnt clash + # Ensure that a random ID does not clash if new_id in self.request_ids: return self._generate_request_id() else: @@ -50,6 +59,13 @@ def _generate_request_id(self) -> str: return new_id def _tracking_header(self) -> Dict[str, str]: + """Generate a header used to track request + + Returns + ------- + Dict[str, str] + A dictionary intended to be merged with all other headers + """ return {'X-PyCord-Testing-ReqId': self._generate_request_id()} async def asyncTearDown(self) -> None: @@ -74,7 +90,7 @@ def run(self, result: Optional[TestResult] = None) -> None: async def proxied_route_request(self, route: Route, allow_through_proxy: bool = False, - ) -> Tuple[HttpParser, Optional[Union[Dict[str, Any], str]]]: + ) -> Tuple[CombinedHttpRequest, Optional[Union[Dict[str, Any], str]]]: """Send a request through the proxy to view request data This function will make a request to a URL through the testing proxy @@ -84,11 +100,11 @@ async def proxied_route_request(self, Parameters ---------- - route : Route + route: Route The API endpoint to make the request to - allow_through_proxy : bool + allow_through_proxy: bool Should the request continue to the destination? (defaults to False) - headers : Optional[dict] + headers: Optional[dict] The headers to be sent with the request, defaults to an empty dict Returns ------- @@ -100,12 +116,15 @@ async def proxied_route_request(self, try: resp = await self.http_client.request(route, ssl=self.ssl, headers={ 'X-Allow-Through': str(int(allow_through_proxy)) - }) + } | self._tracking_header()) except ServerDisconnectedError: # Raised when the proxy blocks request resp = None finally: - req = self.request_queue.get() - return(req, resp) + # Queue module has weird typing that creates errors + req: CombinedHttpRequest = self.request_queue.get() # type: ignore + # Ignore unreachable code warning, as resp is always not None + # provided the try block is executed or ServerDisconnectedError is raised + return (req, resp) # type: ignore async def proxied_request(self, method: str, @@ -113,7 +132,7 @@ async def proxied_request(self, allow_through_proxy: bool = False, headers: Dict[str, str] = {}, body: Union[str, bytes, Dict[str, Any]] = '' - ) -> Tuple[HttpParser, Optional[ClientResponse]]: + ) -> Tuple[CombinedHttpRequest, Optional[ClientResponse]]: """Send a request through the proxy to view request data This function will make a request to a URL through the testing proxy @@ -123,15 +142,15 @@ async def proxied_request(self, Parameters ---------- - method : str - The request method (GET, POST, etc) - url : str + method: str + The request method(GET, POST, etc) + url: str The URL to request - allow_through_proxy : bool + allow_through_proxy: bool Should the request continue to the destination? (defaults to False) - headers : Optional[dict] + headers: Optional[dict] The headers to be sent with the request, defaults to an empty dict - body : Union[str, bytes, Dict[str, Any]] + body: Union[str, bytes, Dict[str, Any]] The body of the request, can either be a string, json data, or raw bytes Returns @@ -166,19 +185,25 @@ async def proxied_request(self, except ServerDisconnectedError: # Raised when the proxy blocks request resp = None finally: - req = self.request_queue.get() - return (req, resp) + # Queue module has weird typing that creates errors + req: CombinedHttpRequest = self.request_queue.get() # type: ignore + # Ignore unreachable code warning, as resp is always not None + # provided the try block is executed or ServerDisconnectedError is raised + return (req, resp) # type: ignore class TestPyCordTestingFixtures(PyCordBaseTestCase): async def test_proxy_sends(self) -> None: + """Tests if the proxy will allow requests through""" _, resp = await self.proxied_request( 'GET', 'https://httpbin.org/get', allow_through_proxy=True) - self.assertEqual(resp.status, 200) + # Ignores the NoneType checking error, as it is guarenteed to not be None + self.assertEqual(resp.status, 200) # type: ignore async def test_proxy_blocks(self) -> None: + """Tests if the proxy blocks requests""" _, resp = await self.proxied_request('GET', 'https://httpbin.org/get', allow_through_proxy=False) self.assertIsNone(resp) From f4d1c8233a57a7a1d0d43ef60965cd96b237f065 Mon Sep 17 00:00:00 2001 From: William Grigor Date: Sat, 6 Nov 2021 02:56:13 -0500 Subject: [PATCH 005/105] =?UTF-8?q?Fixed=20another=20spelling=20error=20?= =?UTF-8?q?=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index f58bed1d95..351c1d99da 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -200,7 +200,7 @@ async def test_proxy_sends(self) -> None: 'GET', 'https://httpbin.org/get', allow_through_proxy=True) - # Ignores the NoneType checking error, as it is guarenteed to not be None + # Ignores the NoneType checking error, as it is guaranteed to not be None self.assertEqual(resp.status, 200) # type: ignore async def test_proxy_blocks(self) -> None: From a15def3869bd4def62e316a72cc51f28412d2a07 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Fri, 7 Jan 2022 05:12:27 +0100 Subject: [PATCH 006/105] x --- build/lib/discord/__init__.py | 78 + build/lib/discord/__main__.py | 303 ++ build/lib/discord/abc.py | 1749 +++++++++++ build/lib/discord/activity.py | 843 +++++ build/lib/discord/appinfo.py | 248 ++ build/lib/discord/asset.py | 417 +++ build/lib/discord/audit_logs.py | 525 ++++ build/lib/discord/backoff.py | 106 + build/lib/discord/bin/libopus-0.x64.dll | Bin 0 -> 441856 bytes build/lib/discord/bin/libopus-0.x86.dll | Bin 0 -> 366080 bytes build/lib/discord/bot.py | 1074 +++++++ build/lib/discord/channel.py | 2136 +++++++++++++ build/lib/discord/client.py | 1653 ++++++++++ build/lib/discord/cog.py | 847 +++++ build/lib/discord/colour.py | 363 +++ build/lib/discord/commands/__init__.py | 29 + build/lib/discord/commands/commands.py | 1330 ++++++++ build/lib/discord/commands/context.py | 200 ++ build/lib/discord/commands/errors.py | 64 + build/lib/discord/commands/permissions.py | 228 ++ build/lib/discord/components.py | 384 +++ build/lib/discord/context_managers.py | 88 + build/lib/discord/embeds.py | 759 +++++ build/lib/discord/emoji.py | 256 ++ build/lib/discord/enums.py | 719 +++++ build/lib/discord/errors.py | 365 +++ build/lib/discord/ext/commands/__init__.py | 19 + build/lib/discord/ext/commands/_types.py | 43 + build/lib/discord/ext/commands/bot.py | 987 ++++++ build/lib/discord/ext/commands/cog.py | 69 + build/lib/discord/ext/commands/context.py | 401 +++ build/lib/discord/ext/commands/converter.py | 1183 +++++++ build/lib/discord/ext/commands/cooldowns.py | 391 +++ build/lib/discord/ext/commands/core.py | 2317 ++++++++++++++ build/lib/discord/ext/commands/errors.py | 916 ++++++ build/lib/discord/ext/commands/flags.py | 619 ++++ build/lib/discord/ext/commands/help.py | 1342 ++++++++ build/lib/discord/ext/commands/view.py | 193 ++ build/lib/discord/ext/pages/__init__.py | 10 + build/lib/discord/ext/pages/pagination.py | 372 +++ build/lib/discord/ext/tasks/__init__.py | 760 +++++ build/lib/discord/file.py | 128 + build/lib/discord/flags.py | 1160 +++++++ build/lib/discord/gateway.py | 912 ++++++ build/lib/discord/guild.py | 3091 +++++++++++++++++++ build/lib/discord/http.py | 2018 ++++++++++++ build/lib/discord/integrations.py | 367 +++ build/lib/discord/interactions.py | 876 ++++++ build/lib/discord/invite.py | 479 +++ build/lib/discord/iterators.py | 754 +++++ build/lib/discord/member.py | 990 ++++++ build/lib/discord/mentions.py | 152 + build/lib/discord/message.py | 1892 ++++++++++++ build/lib/discord/mixins.py | 48 + build/lib/discord/object.py | 108 + build/lib/discord/oggparse.py | 114 + build/lib/discord/opus.py | 455 +++ build/lib/discord/partial_emoji.py | 233 ++ build/lib/discord/permissions.py | 769 +++++ build/lib/discord/player.py | 731 +++++ build/lib/discord/py.typed | 0 build/lib/discord/raw_models.py | 349 +++ build/lib/discord/reaction.py | 212 ++ build/lib/discord/role.py | 493 +++ build/lib/discord/shard.py | 547 ++++ build/lib/discord/stage_instance.py | 177 ++ build/lib/discord/state.py | 1546 ++++++++++ build/lib/discord/sticker.py | 524 ++++ build/lib/discord/team.py | 146 + build/lib/discord/template.py | 317 ++ build/lib/discord/threads.py | 803 +++++ build/lib/discord/types/__init__.py | 10 + build/lib/discord/types/activity.py | 115 + build/lib/discord/types/appinfo.py | 68 + build/lib/discord/types/audit_log.py | 258 ++ build/lib/discord/types/channel.py | 158 + build/lib/discord/types/components.py | 77 + build/lib/discord/types/embed.py | 85 + build/lib/discord/types/emoji.py | 47 + build/lib/discord/types/gateway.py | 42 + build/lib/discord/types/guild.py | 182 ++ build/lib/discord/types/integration.py | 83 + build/lib/discord/types/interactions.py | 237 ++ build/lib/discord/types/invite.py | 100 + build/lib/discord/types/member.py | 65 + build/lib/discord/types/message.py | 139 + build/lib/discord/types/raw_models.py | 108 + build/lib/discord/types/role.py | 50 + build/lib/discord/types/snowflake.py | 29 + build/lib/discord/types/sticker.py | 94 + build/lib/discord/types/team.py | 44 + build/lib/discord/types/template.py | 50 + build/lib/discord/types/threads.py | 76 + build/lib/discord/types/user.py | 49 + build/lib/discord/types/voice.py | 86 + build/lib/discord/types/webhook.py | 71 + build/lib/discord/types/welcome_screen.py | 41 + build/lib/discord/types/widget.py | 64 + build/lib/discord/ui/__init__.py | 15 + build/lib/discord/ui/button.py | 291 ++ build/lib/discord/ui/item.py | 132 + build/lib/discord/ui/select.py | 358 +++ build/lib/discord/ui/view.py | 538 ++++ build/lib/discord/user.py | 499 +++ build/lib/discord/utils.py | 1117 +++++++ build/lib/discord/voice_client.py | 677 ++++ build/lib/discord/webhook/__init__.py | 13 + build/lib/discord/webhook/async_.py | 1686 ++++++++++ build/lib/discord/webhook/sync.py | 1134 +++++++ build/lib/discord/welcome_screen.py | 223 ++ build/lib/discord/widget.py | 302 ++ 111 files changed, 54190 insertions(+) create mode 100644 build/lib/discord/__init__.py create mode 100644 build/lib/discord/__main__.py create mode 100644 build/lib/discord/abc.py create mode 100644 build/lib/discord/activity.py create mode 100644 build/lib/discord/appinfo.py create mode 100644 build/lib/discord/asset.py create mode 100644 build/lib/discord/audit_logs.py create mode 100644 build/lib/discord/backoff.py create mode 100644 build/lib/discord/bin/libopus-0.x64.dll create mode 100644 build/lib/discord/bin/libopus-0.x86.dll create mode 100644 build/lib/discord/bot.py create mode 100644 build/lib/discord/channel.py create mode 100644 build/lib/discord/client.py create mode 100644 build/lib/discord/cog.py create mode 100644 build/lib/discord/colour.py create mode 100644 build/lib/discord/commands/__init__.py create mode 100644 build/lib/discord/commands/commands.py create mode 100644 build/lib/discord/commands/context.py create mode 100644 build/lib/discord/commands/errors.py create mode 100644 build/lib/discord/commands/permissions.py create mode 100644 build/lib/discord/components.py create mode 100644 build/lib/discord/context_managers.py create mode 100644 build/lib/discord/embeds.py create mode 100644 build/lib/discord/emoji.py create mode 100644 build/lib/discord/enums.py create mode 100644 build/lib/discord/errors.py create mode 100644 build/lib/discord/ext/commands/__init__.py create mode 100644 build/lib/discord/ext/commands/_types.py create mode 100644 build/lib/discord/ext/commands/bot.py create mode 100644 build/lib/discord/ext/commands/cog.py create mode 100644 build/lib/discord/ext/commands/context.py create mode 100644 build/lib/discord/ext/commands/converter.py create mode 100644 build/lib/discord/ext/commands/cooldowns.py create mode 100644 build/lib/discord/ext/commands/core.py create mode 100644 build/lib/discord/ext/commands/errors.py create mode 100644 build/lib/discord/ext/commands/flags.py create mode 100644 build/lib/discord/ext/commands/help.py create mode 100644 build/lib/discord/ext/commands/view.py create mode 100644 build/lib/discord/ext/pages/__init__.py create mode 100644 build/lib/discord/ext/pages/pagination.py create mode 100644 build/lib/discord/ext/tasks/__init__.py create mode 100644 build/lib/discord/file.py create mode 100644 build/lib/discord/flags.py create mode 100644 build/lib/discord/gateway.py create mode 100644 build/lib/discord/guild.py create mode 100644 build/lib/discord/http.py create mode 100644 build/lib/discord/integrations.py create mode 100644 build/lib/discord/interactions.py create mode 100644 build/lib/discord/invite.py create mode 100644 build/lib/discord/iterators.py create mode 100644 build/lib/discord/member.py create mode 100644 build/lib/discord/mentions.py create mode 100644 build/lib/discord/message.py create mode 100644 build/lib/discord/mixins.py create mode 100644 build/lib/discord/object.py create mode 100644 build/lib/discord/oggparse.py create mode 100644 build/lib/discord/opus.py create mode 100644 build/lib/discord/partial_emoji.py create mode 100644 build/lib/discord/permissions.py create mode 100644 build/lib/discord/player.py create mode 100644 build/lib/discord/py.typed create mode 100644 build/lib/discord/raw_models.py create mode 100644 build/lib/discord/reaction.py create mode 100644 build/lib/discord/role.py create mode 100644 build/lib/discord/shard.py create mode 100644 build/lib/discord/stage_instance.py create mode 100644 build/lib/discord/state.py create mode 100644 build/lib/discord/sticker.py create mode 100644 build/lib/discord/team.py create mode 100644 build/lib/discord/template.py create mode 100644 build/lib/discord/threads.py create mode 100644 build/lib/discord/types/__init__.py create mode 100644 build/lib/discord/types/activity.py create mode 100644 build/lib/discord/types/appinfo.py create mode 100644 build/lib/discord/types/audit_log.py create mode 100644 build/lib/discord/types/channel.py create mode 100644 build/lib/discord/types/components.py create mode 100644 build/lib/discord/types/embed.py create mode 100644 build/lib/discord/types/emoji.py create mode 100644 build/lib/discord/types/gateway.py create mode 100644 build/lib/discord/types/guild.py create mode 100644 build/lib/discord/types/integration.py create mode 100644 build/lib/discord/types/interactions.py create mode 100644 build/lib/discord/types/invite.py create mode 100644 build/lib/discord/types/member.py create mode 100644 build/lib/discord/types/message.py create mode 100644 build/lib/discord/types/raw_models.py create mode 100644 build/lib/discord/types/role.py create mode 100644 build/lib/discord/types/snowflake.py create mode 100644 build/lib/discord/types/sticker.py create mode 100644 build/lib/discord/types/team.py create mode 100644 build/lib/discord/types/template.py create mode 100644 build/lib/discord/types/threads.py create mode 100644 build/lib/discord/types/user.py create mode 100644 build/lib/discord/types/voice.py create mode 100644 build/lib/discord/types/webhook.py create mode 100644 build/lib/discord/types/welcome_screen.py create mode 100644 build/lib/discord/types/widget.py create mode 100644 build/lib/discord/ui/__init__.py create mode 100644 build/lib/discord/ui/button.py create mode 100644 build/lib/discord/ui/item.py create mode 100644 build/lib/discord/ui/select.py create mode 100644 build/lib/discord/ui/view.py create mode 100644 build/lib/discord/user.py create mode 100644 build/lib/discord/utils.py create mode 100644 build/lib/discord/voice_client.py create mode 100644 build/lib/discord/webhook/__init__.py create mode 100644 build/lib/discord/webhook/async_.py create mode 100644 build/lib/discord/webhook/sync.py create mode 100644 build/lib/discord/welcome_screen.py create mode 100644 build/lib/discord/widget.py diff --git a/build/lib/discord/__init__.py b/build/lib/discord/__init__.py new file mode 100644 index 0000000000..c7169908c3 --- /dev/null +++ b/build/lib/discord/__init__.py @@ -0,0 +1,78 @@ +""" +Discord API Wrapper +~~~~~~~~~~~~~~~~~~~ + +A basic wrapper for the Discord API. + +:copyright: (c) 2015-2021 Rapptz & (c) 2021-present Pycord Development +:license: MIT, see LICENSE for more details. + +""" + +__title__ = 'discord' +__author__ = 'Pycord Development' +__license__ = 'MIT' +__copyright__ = 'Copyright 2015-2021 Rapptz & Copyright 2021-present Pycord Development' +__version__ = '2.0.0a' + +__path__ = __import__('pkgutil').extend_path(__path__, __name__) + +import logging +from typing import NamedTuple, Literal + +from .client import * +from .appinfo import * +from .user import * +from .emoji import * +from .partial_emoji import * +from .activity import * +from .channel import * +from .guild import * +from .flags import * +from .member import * +from .message import * +from .asset import * +from .errors import * +from .permissions import * +from .role import * +from .file import * +from .colour import * +from .integrations import * +from .invite import * +from .template import * +from .widget import * +from .object import * +from .reaction import * +from . import utils, opus, abc, ui +from .enums import * +from .embeds import * +from .mentions import * +from .shard import * +from .player import * +from .webhook import * +from .voice_client import * +from .audit_logs import * +from .raw_models import * +from .team import * +from .sticker import * +from .stage_instance import * +from .interactions import * +from .components import * +from .threads import * +from .bot import * +from .commands import * +from .cog import Cog +from .welcome_screen import * + + +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()) diff --git a/build/lib/discord/__main__.py b/build/lib/discord/__main__.py new file mode 100644 index 0000000000..3f043e5c37 --- /dev/null +++ b/build/lib/discord/__main__.py @@ -0,0 +1,303 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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. +""" + +import argparse +import sys +from pathlib import Path + +from typing import Tuple + +import discord +import pkg_resources +import aiohttp +import platform + +def show_version() -> None: + entries = [] + + entries.append('- Python v{0.major}.{0.minor}.{0.micro}-{0.releaselevel}'.format(sys.version_info)) + version_info = discord.version_info + entries.append('- py-cord v{0.major}.{0.minor}.{0.micro}-{0.releaselevel}'.format(version_info)) + if version_info.releaselevel != 'final': + pkg = pkg_resources.get_distribution('py-cord') + if pkg: + entries.append(f' - py-cord pkg_resources: v{pkg.version}') + + entries.append(f'- aiohttp v{aiohttp.__version__}') + uname = platform.uname() + entries.append('- system info: {0.system} {0.release} {0.version}'.format(uname)) + print('\n'.join(entries)) + +def core(parser, args) -> None: + if args.version: + show_version() + +_bot_template = """#!/usr/bin/env python3 + +from discord.ext import commands +import discord +import config + +class Bot(commands.{base}): + def __init__(self, **kwargs): + super().__init__(command_prefix=commands.when_mentioned_or('{prefix}'), **kwargs) + for cog in config.cogs: + try: + self.load_extension(cog) + except Exception as exc: + print(f'Could not load extension {{cog}} due to {{exc.__class__.__name__}}: {{exc}}') + + async def on_ready(self): + print(f'Logged on as {{self.user}} (ID: {{self.user.id}})') + + +bot = Bot() + +# write general commands here + +bot.run(config.token) +""" + +_gitignore_template = """# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Our configuration files +config.py +""" + +_cog_template = '''from discord.ext import commands +import discord + +class {name}(commands.Cog{attrs}): + """The description for {name} goes here.""" + + def __init__(self, bot): + self.bot = bot +{extra} +def setup(bot): + bot.add_cog({name}(bot)) +''' + +_cog_extras = ''' + def cog_unload(self): + # clean up logic goes here + pass + + async def cog_check(self, ctx): + # checks that apply to every command in here + return True + + async def bot_check(self, ctx): + # checks that apply to every command to the bot + return True + + async def bot_check_once(self, ctx): + # check that apply to every command but is guaranteed to be called only once + return True + + async def cog_command_error(self, ctx, error): + # error handling to every command in here + pass + + async def cog_before_invoke(self, ctx): + # called before a command is called here + pass + + async def cog_after_invoke(self, ctx): + # called after a command is called here + pass + +''' + + +# certain file names and directory names are forbidden +# see: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx +# although some of this doesn't apply to Linux, we might as well be consistent +_base_table = { + '<': '-', + '>': '-', + ':': '-', + '"': '-', + # '/': '-', these are fine + # '\\': '-', + '|': '-', + '?': '-', + '*': '-', +} + +# NUL (0) and 1-31 are disallowed +_base_table.update((chr(i), None) for i in range(32)) + +_translation_table = str.maketrans(_base_table) + +def to_path(parser, name, *, replace_spaces=False) -> Path: + if isinstance(name, Path): + return name + + if sys.platform == 'win32': + forbidden = ('CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', \ + 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9') + if len(name) <= 4 and name.upper() in forbidden: + parser.error('invalid directory name given, use a different one') + + name = name.translate(_translation_table) + if replace_spaces: + name = name.replace(' ', '-') + return Path(name) + +def newbot(parser, args) -> None: + new_directory = to_path(parser, args.directory) / to_path(parser, args.name) + + # as a note exist_ok for Path is a 3.5+ only feature + # since we already checked above that we're >3.5 + try: + new_directory.mkdir(exist_ok=True, parents=True) + except OSError as exc: + parser.error(f'could not create our bot directory ({exc})') + + cogs = new_directory / 'cogs' + + try: + cogs.mkdir(exist_ok=True) + init = cogs / '__init__.py' + init.touch() + except OSError as exc: + print(f'warning: could not create cogs directory ({exc})') + + try: + with open(str(new_directory / 'config.py'), 'w', encoding='utf-8') as fp: + fp.write('token = "place your token here"\ncogs = []\n') + except OSError as exc: + parser.error(f'could not create config file ({exc})') + + try: + with open(str(new_directory / 'bot.py'), 'w', encoding='utf-8') as fp: + base = 'Bot' if not args.sharded else 'AutoShardedBot' + fp.write(_bot_template.format(base=base, prefix=args.prefix)) + except OSError as exc: + parser.error(f'could not create bot file ({exc})') + + if not args.no_git: + try: + with open(str(new_directory / '.gitignore'), 'w', encoding='utf-8') as fp: + fp.write(_gitignore_template) + except OSError as exc: + print(f'warning: could not create .gitignore file ({exc})') + + print('successfully made bot at', new_directory) + +def newcog(parser, args) -> None: + cog_dir = to_path(parser, args.directory) + try: + cog_dir.mkdir(exist_ok=True) + except OSError as exc: + print(f'warning: could not create cogs directory ({exc})') + + directory = cog_dir / to_path(parser, args.name) + directory = directory.with_suffix('.py') + try: + with open(str(directory), 'w', encoding='utf-8') as fp: + attrs = '' + extra = _cog_extras if args.full else '' + if args.class_name: + name = args.class_name + else: + name = str(directory.stem) + if '-' in name or '_' in name: + translation = str.maketrans('-_', ' ') + name = name.translate(translation).title().replace(' ', '') + else: + name = name.title() + + if args.display_name: + attrs += f', name="{args.display_name}"' + if args.hide_commands: + attrs += ', command_attrs=dict(hidden=True)' + fp.write(_cog_template.format(name=name, extra=extra, attrs=attrs)) + except OSError as exc: + parser.error(f'could not create cog file ({exc})') + else: + print('successfully made cog at', directory) + +def add_newbot_args(subparser: argparse._SubParsersAction) -> None: + parser = subparser.add_parser('newbot', help='creates a command bot project quickly') + parser.set_defaults(func=newbot) + + parser.add_argument('name', help='the bot project name') + parser.add_argument('directory', help='the directory to place it in (default: .)', nargs='?', default=Path.cwd()) + parser.add_argument('--prefix', help='the bot prefix (default: $)', default='$', metavar='') + parser.add_argument('--sharded', help='whether to use AutoShardedBot', action='store_true') + parser.add_argument('--no-git', help='do not create a .gitignore file', action='store_true', dest='no_git') + +def add_newcog_args(subparser: argparse._SubParsersAction) -> None: + parser = subparser.add_parser('newcog', help='creates a new cog template quickly') + parser.set_defaults(func=newcog) + + parser.add_argument('name', help='the cog name') + parser.add_argument('directory', help='the directory to place it in (default: cogs)', nargs='?', default=Path('cogs')) + parser.add_argument('--class-name', help='the class name of the cog (default: )', dest='class_name') + parser.add_argument('--display-name', help='the cog name (default: )') + parser.add_argument('--hide-commands', help='whether to hide all commands in the cog', action='store_true') + parser.add_argument('--full', help='add all special methods as well', action='store_true') + +def parse_args() -> Tuple[argparse.ArgumentParser, argparse.Namespace]: + parser = argparse.ArgumentParser(prog='discord', description='Tools for helping with Pycord') + parser.add_argument('-v', '--version', action='store_true', help='shows the library version') + parser.set_defaults(func=core) + + subparser = parser.add_subparsers(dest='subcommand', title='subcommands') + add_newbot_args(subparser) + add_newcog_args(subparser) + return parser, parser.parse_args() + +def main() -> None: + parser, args = parse_args() + args.func(parser, args) + +if __name__ == '__main__': + main() diff --git a/build/lib/discord/abc.py b/build/lib/discord/abc.py new file mode 100644 index 0000000000..f6e876aca6 --- /dev/null +++ b/build/lib/discord/abc.py @@ -0,0 +1,1749 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import copy +import asyncio +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + TYPE_CHECKING, + Protocol, + Sequence, + Tuple, + TypeVar, + Union, + overload, + runtime_checkable, +) + +from .iterators import HistoryIterator +from .context_managers import Typing +from .enums import ChannelType +from .errors import InvalidArgument, ClientException +from .mentions import AllowedMentions +from .permissions import PermissionOverwrite, Permissions +from .role import Role +from .invite import Invite +from .file import File +from .voice_client import VoiceClient, VoiceProtocol +from .sticker import GuildSticker, StickerItem +from . import utils + +__all__ = ( + 'Snowflake', + 'User', + 'PrivateChannel', + 'GuildChannel', + 'Messageable', + 'Connectable', + 'Mentionable' +) + +T = TypeVar('T', bound=VoiceProtocol) + +if TYPE_CHECKING: + from datetime import datetime + + from .client import Client + from .user import ClientUser + from .asset import Asset + from .state import ConnectionState + from .guild import Guild + from .member import Member + from .channel import CategoryChannel + from .embeds import Embed + from .message import Message, MessageReference, PartialMessage + from .channel import TextChannel, DMChannel, GroupChannel, PartialMessageable + from .threads import Thread + from .enums import InviteTarget + 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] + +MISSING = utils.MISSING + + +class _Undefined: + def __repr__(self) -> str: + return 'see-below' + + +_undefined: Any = _Undefined() + + +@runtime_checkable +class Snowflake(Protocol): + """An ABC that details the common operations on a Discord model. + + Almost all :ref:`Discord models ` meet this + abstract base class. + + If you want to create a snowflake on your own, consider using + :class:`.Object`. + + Attributes + ----------- + id: :class:`int` + The model's unique ID. + """ + + __slots__ = () + id: int + + +@runtime_checkable +class User(Snowflake, Protocol): + """An ABC that details the common operations on a Discord user. + + The following implement this ABC: + + - :class:`~discord.User` + - :class:`~discord.ClientUser` + - :class:`~discord.Member` + + This ABC must also implement :class:`~discord.abc.Snowflake`. + + Attributes + ----------- + name: :class:`str` + The user's username. + discriminator: :class:`str` + The user's discriminator. + avatar: :class:`~discord.Asset` + The avatar asset the user has. + bot: :class:`bool` + If the user is a bot account. + """ + + __slots__ = () + + name: str + discriminator: str + avatar: Asset + bot: bool + + @property + def display_name(self) -> str: + """:class:`str`: Returns the user's display name.""" + raise NotImplementedError + + @property + def mention(self) -> str: + """:class:`str`: Returns a string that allows you to mention the given user.""" + raise NotImplementedError + + +@runtime_checkable +class PrivateChannel(Snowflake, Protocol): + """An ABC that details the common operations on a private Discord channel. + + The following implement this ABC: + + - :class:`~discord.DMChannel` + - :class:`~discord.GroupChannel` + + This ABC must also implement :class:`~discord.abc.Snowflake`. + + Attributes + ----------- + me: :class:`~discord.ClientUser` + The user presenting yourself. + """ + + __slots__ = () + + me: ClientUser + + +class _Overwrites: + __slots__ = ('id', 'allow', 'deny', 'type') + + ROLE = 0 + MEMBER = 1 + + def __init__(self, data: PermissionOverwritePayload): + self.id: int = int(data['id']) + self.allow: int = int(data.get('allow', 0)) + self.deny: int = int(data.get('deny', 0)) + self.type: OverwriteType = data['type'] + + def _asdict(self) -> PermissionOverwritePayload: + return { + 'id': self.id, + 'allow': str(self.allow), + 'deny': str(self.deny), + 'type': self.type, + } + + def is_role(self) -> bool: + return self.type == self.ROLE + + def is_member(self) -> bool: + return self.type == self.MEMBER + + +GCH = TypeVar('GCH', bound='GuildChannel') + + +class GuildChannel: + """An ABC that details the common operations on a Discord guild channel. + + The following implement this ABC: + + - :class:`~discord.TextChannel` + - :class:`~discord.VoiceChannel` + - :class:`~discord.CategoryChannel` + - :class:`~discord.StageChannel` + + This ABC must also implement :class:`~discord.abc.Snowflake`. + + Attributes + ----------- + name: :class:`str` + The channel name. + guild: :class:`~discord.Guild` + The guild the channel belongs to. + position: :class:`int` + The position in the channel list. This is a number that starts at 0. + e.g. the top channel is position 0. + """ + + __slots__ = () + + id: int + name: str + guild: Guild + type: ChannelType + position: int + category_id: Optional[int] + _state: ConnectionState + _overwrites: List[_Overwrites] + + if TYPE_CHECKING: + + def __init__(self, *, state: ConnectionState, guild: Guild, data: Dict[str, Any]): + ... + + def __str__(self) -> str: + return self.name + + @property + def _sorting_bucket(self) -> int: + raise NotImplementedError + + def _update(self, guild: Guild, data: Dict[str, Any]) -> None: + raise NotImplementedError + + async def _move( + self, + position: int, + parent_id: Optional[Any] = None, + lock_permissions: bool = False, + *, + reason: Optional[str], + ) -> None: + if position < 0: + raise InvalidArgument('Channel position cannot be less than 0.') + + http = self._state.http + bucket = self._sorting_bucket + channels: List[GuildChannel] = [c for c in self.guild.channels if c._sorting_bucket == bucket] + + channels.sort(key=lambda c: c.position) + + try: + # remove ourselves from the channel list + channels.remove(self) + except ValueError: + # not there somehow lol + return + else: + index = next((i for i, c in enumerate(channels) if c.position >= position), len(channels)) + # add ourselves at our designated position + channels.insert(index, self) + + payload = [] + for index, c in enumerate(channels): + d: Dict[str, Any] = {'id': c.id, 'position': index} + if parent_id is not _undefined and c.id == self.id: + d.update(parent_id=parent_id, lock_permissions=lock_permissions) + payload.append(d) + + await http.bulk_channel_update(self.guild.id, payload, reason=reason) + + async def _edit(self, options: Dict[str, Any], reason: Optional[str]) -> Optional[ChannelPayload]: + try: + parent = options.pop('category') + except KeyError: + parent_id = _undefined + else: + parent_id = parent and parent.id + + try: + options['rate_limit_per_user'] = options.pop('slowmode_delay') + except KeyError: + pass + + try: + rtc_region = options.pop('rtc_region') + except KeyError: + pass + else: + options['rtc_region'] = None if rtc_region is None else str(rtc_region) + + try: + video_quality_mode = options.pop('video_quality_mode') + except KeyError: + pass + else: + options['video_quality_mode'] = int(video_quality_mode) + + lock_permissions = options.pop('sync_permissions', False) + + try: + position = options.pop('position') + except KeyError: + if parent_id is not _undefined: + if lock_permissions: + category = self.guild.get_channel(parent_id) + if category: + options['permission_overwrites'] = [c._asdict() for c in category._overwrites] + options['parent_id'] = parent_id + elif lock_permissions and self.category_id is not None: + # 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 + category = self.guild.get_channel(self.category_id) + if category: + options['permission_overwrites'] = [c._asdict() for c in category._overwrites] + else: + await self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason) + + overwrites = options.get('overwrites', None) + if overwrites is not None: + perms = [] + for target, perm in overwrites.items(): + if not isinstance(perm, PermissionOverwrite): + raise InvalidArgument(f'Expected PermissionOverwrite received {perm.__class__.__name__}') + + allow, deny = perm.pair() + payload = { + 'allow': allow.value, + 'deny': deny.value, + 'id': target.id, + } + + if isinstance(target, Role): + payload['type'] = _Overwrites.ROLE + else: + payload['type'] = _Overwrites.MEMBER + + perms.append(payload) + options['permission_overwrites'] = perms + + try: + ch_type = options['type'] + except KeyError: + pass + else: + if not isinstance(ch_type, ChannelType): + raise InvalidArgument('type field must be of type ChannelType') + options['type'] = ch_type.value + + if options: + return await self._state.http.edit_channel(self.id, reason=reason, **options) + + def _fill_overwrites(self, data: GuildChannelPayload) -> None: + self._overwrites = [] + everyone_index = 0 + everyone_id = self.guild.id + + for index, overridden in enumerate(data.get('permission_overwrites', [])): + overwrite = _Overwrites(overridden) + self._overwrites.append(overwrite) + + if overwrite.type == _Overwrites.MEMBER: + continue + + if overwrite.id == everyone_id: + # the @everyone role is not guaranteed to be the first one + # in the list of permission overwrites, however the permission + # resolution code kind of requires that it is the first one in + # the list since it is special. So we need the index so we can + # swap it to be the first one. + everyone_index = index + + # do the swap + tmp = self._overwrites + if tmp: + tmp[everyone_index], tmp[0] = tmp[0], tmp[everyone_index] + + @property + def changed_roles(self) -> List[Role]: + """List[:class:`~discord.Role`]: Returns a list of roles that have been overridden from + their default values in the :attr:`~discord.Guild.roles` attribute.""" + ret = [] + g = self.guild + for overwrite in filter(lambda o: o.is_role(), self._overwrites): + role = g.get_role(overwrite.id) + if role is None: + continue + + role = copy.copy(role) + role.permissions.handle_overwrite(overwrite.allow, overwrite.deny) + ret.append(role) + return ret + + @property + def mention(self) -> str: + """:class:`str`: The string that allows you to mention the channel.""" + return f'<#{self.id}>' + + @property + def created_at(self) -> datetime: + """:class:`datetime.datetime`: Returns the channel's creation time in UTC.""" + return utils.snowflake_time(self.id) + + def overwrites_for(self, obj: Union[Role, User]) -> PermissionOverwrite: + """Returns the channel-specific overwrites for a member or a role. + + Parameters + ----------- + obj: Union[:class:`~discord.Role`, :class:`~discord.abc.User`] + The role or user denoting + whose overwrite to get. + + Returns + --------- + :class:`~discord.PermissionOverwrite` + The permission overwrites for this object. + """ + + if isinstance(obj, User): + predicate = lambda p: p.is_member() + elif isinstance(obj, Role): + predicate = lambda p: p.is_role() + else: + predicate = lambda p: True + + for overwrite in filter(predicate, self._overwrites): + if overwrite.id == obj.id: + allow = Permissions(overwrite.allow) + deny = Permissions(overwrite.deny) + return PermissionOverwrite.from_pair(allow, deny) + + return PermissionOverwrite() + + @property + def overwrites(self) -> Dict[Union[Role, Member], PermissionOverwrite]: + """Returns all of the channel's overwrites. + + This is returned as a dictionary where the key contains the target which + can be either a :class:`~discord.Role` or a :class:`~discord.Member` and the value is the + overwrite as a :class:`~discord.PermissionOverwrite`. + + Returns + -------- + Dict[Union[:class:`~discord.Role`, :class:`~discord.Member`], :class:`~discord.PermissionOverwrite`] + The channel's permission overwrites. + """ + ret = {} + for ow in self._overwrites: + allow = Permissions(ow.allow) + deny = Permissions(ow.deny) + overwrite = PermissionOverwrite.from_pair(allow, deny) + target = None + + if ow.is_role(): + target = self.guild.get_role(ow.id) + elif ow.is_member(): + target = self.guild.get_member(ow.id) + + # TODO: There is potential data loss here in the non-chunked + # case, i.e. target is None because get_member returned nothing. + # This can be fixed with a slight breaking change to the return type, + # i.e. adding discord.Object to the list of it + # However, for now this is an acceptable compromise. + if target is not None: + ret[target] = overwrite + return ret + + @property + def category(self) -> Optional[CategoryChannel]: + """Optional[:class:`~discord.CategoryChannel`]: The category this channel belongs to. + + If there is no category then this is ``None``. + """ + return self.guild.get_channel(self.category_id) # type: ignore + + @property + def permissions_synced(self) -> bool: + """:class:`bool`: Whether or not the permissions for this channel are synced with the + category it belongs to. + + If there is no category then this is ``False``. + + .. versionadded:: 1.3 + """ + if self.category_id is None: + return False + + category = self.guild.get_channel(self.category_id) + return bool(category and category.overwrites == self.overwrites) + + def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + """Handles permission resolution for the :class:`~discord.Member` + or :class:`~discord.Role`. + + This function takes into consideration the following cases: + + - Guild owner + - Guild roles + - Channel overrides + - Member overrides + + If a :class:`~discord.Role` is passed, then it checks the permissions + someone with that role would have, which is essentially: + + - The default role permissions + - The permissions of the role used as a parameter + - The default role permission overwrites + - The permission overwrites of the role used as a parameter + + .. versionchanged:: 2.0 + The object passed in can now be a role object. + + Parameters + ---------- + obj: Union[:class:`~discord.Member`, :class:`~discord.Role`] + The object to resolve permissions for. This could be either + a member or a role. If it's a role then member overwrites + are not computed. + + Returns + ------- + :class:`~discord.Permissions` + The resolved permissions for the member or role. + """ + + # The current cases can be explained as: + # Guild owner get all permissions -- no questions asked. Otherwise... + # The @everyone role gets the first application. + # After that, the applied roles that the user has in the channel + # (or otherwise) are then OR'd together. + # After the role permissions are resolved, the member permissions + # have to take into effect. + # After all that is done.. you have to do the following: + + # If manage permissions is True, then all permissions are set to True. + + # The operation first takes into consideration the denied + # and then the allowed. + + if self.guild.owner_id == obj.id: + return Permissions.all() + + default = self.guild.default_role + base = Permissions(default.permissions.value) + + # Handle the role case first + 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(): + return base + + overwrite = utils.get(self._overwrites, type=_Overwrites.ROLE, id=obj.id) + if overwrite is not None: + base.handle_overwrite(overwrite.allow, overwrite.deny) + + return base + + roles = obj._roles + get_role = self.guild.get_role + + # Apply guild roles that the member has. + for role_id in roles: + role = get_role(role_id) + if role is not None: + base.value |= role._permissions + + # Guild-wide Administrator -> True for everything + # Bypass all channel-specific overrides + 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) + remaining_overwrites = self._overwrites[1:] + else: + remaining_overwrites = self._overwrites + except IndexError: + remaining_overwrites = self._overwrites + + denies = 0 + allows = 0 + + # Apply channel specific role permission overwrites + for overwrite in remaining_overwrites: + if overwrite.is_role() and roles.has(overwrite.id): + denies |= overwrite.deny + allows |= overwrite.allow + + base.handle_overwrite(allow=allows, deny=denies) + + # Apply member specific permission overwrites + for overwrite in remaining_overwrites: + if overwrite.is_member() and overwrite.id == obj.id: + base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny) + break + + # if you can't send a message in a channel then you can't have certain + # permissions as well + if not base.send_messages: + base.send_tts_messages = False + base.mention_everyone = False + base.embed_links = False + base.attach_files = False + + # if you can't read a channel then you have no permissions there + if not base.read_messages: + denied = Permissions.all_channel() + base.value &= ~denied.value + + return base + + async def delete(self, *, reason: Optional[str] = None) -> None: + """|coro| + + Deletes the channel. + + You must have :attr:`~discord.Permissions.manage_channels` permission to use this. + + Parameters + ----------- + reason: Optional[:class:`str`] + The reason for deleting this channel. + Shows up on the audit log. + + Raises + ------- + ~discord.Forbidden + You do not have proper permissions to delete the channel. + ~discord.NotFound + The channel was not found or was already deleted. + ~discord.HTTPException + Deleting the channel failed. + """ + await self._state.http.delete_channel(self.id, reason=reason) + + @overload + async def set_permissions( + self, + target: Union[Member, Role], + *, + overwrite: Optional[Union[PermissionOverwrite, _Undefined]] = ..., + reason: Optional[str] = ..., + ) -> None: + ... + + @overload + async def set_permissions( + self, + target: Union[Member, Role], + *, + reason: Optional[str] = ..., + **permissions: bool, + ) -> None: + ... + + async def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions): + r"""|coro| + + Sets the channel specific permission overwrites for a target in the + channel. + + The ``target`` parameter should either be a :class:`~discord.Member` or a + :class:`~discord.Role` that belongs to guild. + + The ``overwrite`` parameter, if given, must either be ``None`` or + :class:`~discord.PermissionOverwrite`. For convenience, you can pass in + keyword arguments denoting :class:`~discord.Permissions` attributes. If this is + done, then you cannot mix the keyword arguments with the ``overwrite`` + parameter. + + If the ``overwrite`` parameter is ``None``, then the permission + overwrites are deleted. + + You must have the :attr:`~discord.Permissions.manage_roles` permission to use this. + + .. note:: + + This method *replaces* the old overwrites with the ones given. + + Examples + ---------- + + Setting allow and deny: :: + + await message.channel.set_permissions(message.author, read_messages=True, + send_messages=False) + + Deleting overwrites :: + + await channel.set_permissions(member, overwrite=None) + + Using :class:`~discord.PermissionOverwrite` :: + + overwrite = discord.PermissionOverwrite() + overwrite.send_messages = False + overwrite.read_messages = True + await channel.set_permissions(member, overwrite=overwrite) + + Parameters + ----------- + target: Union[:class:`~discord.Member`, :class:`~discord.Role`] + The member or role to overwrite permissions for. + overwrite: Optional[:class:`~discord.PermissionOverwrite`] + The permissions to allow and deny to the target, or ``None`` to + delete the overwrite. + \*\*permissions + A keyword argument list of permissions to set for ease of use. + Cannot be mixed with ``overwrite``. + reason: Optional[:class:`str`] + The reason for doing this action. Shows up on the audit log. + + Raises + ------- + ~discord.Forbidden + You do not have permissions to edit channel specific permissions. + ~discord.HTTPException + Editing channel specific permissions failed. + ~discord.NotFound + The role or member being edited is not part of the guild. + ~discord.InvalidArgument + The overwrite parameter invalid or the target type was not + :class:`~discord.Role` or :class:`~discord.Member`. + """ + + http = self._state.http + + if isinstance(target, User): + perm_type = _Overwrites.MEMBER + elif isinstance(target, Role): + perm_type = _Overwrites.ROLE + else: + raise InvalidArgument('target parameter must be either Member or Role') + + if overwrite is _undefined: + if len(permissions) == 0: + raise InvalidArgument('No overwrite provided.') + try: + overwrite = PermissionOverwrite(**permissions) + except (ValueError, TypeError): + raise InvalidArgument('Invalid permissions given to keyword arguments.') + else: + if len(permissions) > 0: + raise InvalidArgument('Cannot mix overwrite and keyword arguments.') + + # TODO: wait for event + + if overwrite is None: + await http.delete_channel_permissions(self.id, target.id, reason=reason) + elif isinstance(overwrite, PermissionOverwrite): + (allow, deny) = overwrite.pair() + await http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason) + else: + raise InvalidArgument('Invalid overwrite type provided.') + + async def _clone_impl( + self: GCH, + base_attrs: Dict[str, Any], + *, + name: Optional[str] = None, + reason: Optional[str] = None, + ) -> GCH: + base_attrs['permission_overwrites'] = [x._asdict() for x in self._overwrites] + base_attrs['parent_id'] = self.category_id + base_attrs['name'] = name or self.name + guild_id = self.guild.id + cls = self.__class__ + data = await self._state.http.create_channel(guild_id, self.type.value, reason=reason, **base_attrs) + obj = cls(state=self._state, guild=self.guild, data=data) + + # temporarily add it to the cache + self.guild._channels[obj.id] = obj # type: ignore + return obj + + async def clone(self: GCH, *, name: Optional[str] = None, reason: Optional[str] = None) -> GCH: + """|coro| + + Clones this channel. This creates a channel with the same properties + as this channel. + + You must have the :attr:`~discord.Permissions.manage_channels` permission to + do this. + + .. versionadded:: 1.1 + + Parameters + ------------ + name: Optional[:class:`str`] + The name of the new channel. If not provided, defaults to this + channel name. + reason: Optional[:class:`str`] + The reason for cloning this channel. Shows up on the audit log. + + Raises + ------- + ~discord.Forbidden + You do not have the proper permissions to create this channel. + ~discord.HTTPException + Creating the channel failed. + + Returns + -------- + :class:`.abc.GuildChannel` + The channel that was created. + """ + raise NotImplementedError + + @overload + async def move( + self, + *, + beginning: bool, + offset: int = MISSING, + category: Optional[Snowflake] = MISSING, + sync_permissions: bool = MISSING, + reason: Optional[str] = MISSING, + ) -> None: + ... + + @overload + async def move( + self, + *, + end: bool, + offset: int = MISSING, + category: Optional[Snowflake] = MISSING, + sync_permissions: bool = MISSING, + reason: str = MISSING, + ) -> None: + ... + + @overload + async def move( + self, + *, + before: Snowflake, + offset: int = MISSING, + category: Optional[Snowflake] = MISSING, + sync_permissions: bool = MISSING, + reason: str = MISSING, + ) -> None: + ... + + @overload + async def move( + self, + *, + after: Snowflake, + offset: int = MISSING, + category: Optional[Snowflake] = MISSING, + sync_permissions: bool = MISSING, + reason: str = MISSING, + ) -> None: + ... + + async def move(self, **kwargs) -> None: + """|coro| + + A rich interface to help move a channel relative to other channels. + + If exact position movement is required, ``edit`` should be used instead. + + You must have the :attr:`~discord.Permissions.manage_channels` permission to + do this. + + .. note:: + + Voice channels will always be sorted below text channels. + This is a Discord limitation. + + .. versionadded:: 1.7 + + Parameters + ------------ + beginning: :class:`bool` + Whether to move the channel to the beginning of the + channel list (or category if given). + This is mutually exclusive with ``end``, ``before``, and ``after``. + end: :class:`bool` + Whether to move the channel to the end of the + channel list (or category if given). + This is mutually exclusive with ``beginning``, ``before``, and ``after``. + before: :class:`~discord.abc.Snowflake` + The channel that should be before our current channel. + This is mutually exclusive with ``beginning``, ``end``, and ``after``. + after: :class:`~discord.abc.Snowflake` + The channel that should be after our current channel. + This is mutually exclusive with ``beginning``, ``end``, and ``before``. + offset: :class:`int` + The number of channels to offset the move by. For example, + an offset of ``2`` with ``beginning=True`` would move + it 2 after the beginning. A positive number moves it below + while a negative number moves it above. Note that this + number is relative and computed after the ``beginning``, + ``end``, ``before``, and ``after`` parameters. + category: Optional[:class:`~discord.abc.Snowflake`] + The category to move this channel under. + If ``None`` is given then it moves it out of the category. + This parameter is ignored if moving a category channel. + sync_permissions: :class:`bool` + Whether to sync the permissions with the category (if given). + reason: :class:`str` + The reason for the move. + + Raises + ------- + InvalidArgument + An invalid position was given or a bad mix of arguments were passed. + Forbidden + You do not have permissions to move the channel. + HTTPException + Moving the channel failed. + """ + + if not kwargs: + return + + beginning, end = kwargs.get('beginning'), kwargs.get('end') + before, after = kwargs.get('before'), kwargs.get('after') + offset = kwargs.get('offset', 0) + if sum(bool(a) for a in (beginning, end, before, after)) > 1: + raise InvalidArgument('Only one of [before, after, end, beginning] can be used.') + + bucket = self._sorting_bucket + parent_id = kwargs.get('category', MISSING) + # fmt: off + channels: List[GuildChannel] + if parent_id not in (MISSING, None): + parent_id = parent_id.id + channels = [ + ch + for ch in self.guild.channels + if ch._sorting_bucket == bucket + and ch.category_id == parent_id + ] + else: + channels = [ + ch + for ch in self.guild.channels + if ch._sorting_bucket == bucket + and ch.category_id == self.category_id + ] + # fmt: on + + channels.sort(key=lambda c: (c.position, c.id)) + + try: + # Try to remove ourselves from the channel list + channels.remove(self) + except ValueError: + # If we're not there then it's probably due to not being in the category + pass + + index = None + if beginning: + index = 0 + elif end: + index = len(channels) + elif before: + index = next((i for i, c in enumerate(channels) if c.id == before.id), None) + elif after: + index = next((i + 1 for i, c in enumerate(channels) if c.id == after.id), None) + + if index is None: + raise InvalidArgument('Could not resolve appropriate move position') + + channels.insert(max((index + offset), 0), self) + payload = [] + lock_permissions = kwargs.get('sync_permissions', False) + reason = kwargs.get('reason') + for index, channel in enumerate(channels): + d = {'id': channel.id, 'position': index} + if parent_id is not MISSING and channel.id == self.id: + d.update(parent_id=parent_id, lock_permissions=lock_permissions) + payload.append(d) + + await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason) + + async def create_invite( + self, + *, + reason: Optional[str] = None, + max_age: int = 0, + max_uses: int = 0, + temporary: bool = False, + unique: bool = True, + target_type: Optional[InviteTarget] = None, + target_user: Optional[User] = None, + target_application_id: Optional[int] = None, + ) -> Invite: + """|coro| + + Creates an instant invite from a text or voice channel. + + You must have the :attr:`~discord.Permissions.create_instant_invite` permission to + do this. + + Parameters + ------------ + max_age: :class:`int` + How long the invite should last in seconds. If it's 0 then the invite + doesn't expire. Defaults to ``0``. + max_uses: :class:`int` + How many uses the invite could be used for. If it's 0 then there + are unlimited uses. Defaults to ``0``. + temporary: :class:`bool` + Denotes that the invite grants temporary membership + (i.e. they get kicked after they disconnect). Defaults to ``False``. + unique: :class:`bool` + Indicates if a unique invite URL should be created. Defaults to True. + If this is set to ``False`` then it will return a previously created + invite. + reason: Optional[:class:`str`] + The reason for creating this invite. Shows up on the audit log. + target_type: Optional[:class:`.InviteTarget`] + The type of target for the voice channel invite, if any. + + .. versionadded:: 2.0 + + target_user: Optional[:class:`User`] + The user whose stream to display for this invite, required if `target_type` is `TargetType.stream`. The user must be streaming in the channel. + + .. versionadded:: 2.0 + + target_application_id:: Optional[:class:`int`] + The id of the embedded application for the invite, required if `target_type` is `TargetType.embedded_application`. + + .. versionadded:: 2.0 + + Raises + ------- + ~discord.HTTPException + Invite creation failed. + + ~discord.NotFound + The channel that was passed is a category or an invalid channel. + + Returns + -------- + :class:`~discord.Invite` + The invite that was created. + """ + + data = await self._state.http.create_invite( + self.id, + reason=reason, + max_age=max_age, + max_uses=max_uses, + temporary=temporary, + unique=unique, + target_type=target_type.value if target_type else None, + target_user_id=target_user.id if target_user else None, + target_application_id=target_application_id, + ) + return Invite.from_incomplete(data=data, state=self._state) + + async def invites(self) -> List[Invite]: + """|coro| + + Returns a list of all active instant invites from this channel. + + You must have :attr:`~discord.Permissions.manage_channels` to get this information. + + Raises + ------- + ~discord.Forbidden + You do not have proper permissions to get the information. + ~discord.HTTPException + An error occurred while fetching the information. + + Returns + ------- + List[:class:`~discord.Invite`] + The list of invites that are currently active. + """ + + state = self._state + data = await state.http.invites_from_channel(self.id) + guild = self.guild + return [Invite(state=state, data=invite, channel=self, guild=guild) for invite in data] + + +class Messageable: + """An ABC that details the common operations on a model that can send messages. + + The following implement this ABC: + + - :class:`~discord.TextChannel` + - :class:`~discord.DMChannel` + - :class:`~discord.GroupChannel` + - :class:`~discord.User` + - :class:`~discord.Member` + - :class:`~discord.ext.commands.Context` + - :class:`~discord.Thread` + - :class:`~discord.ApplicationContext` + """ + + __slots__ = () + _state: ConnectionState + + async def _get_channel(self) -> MessageableChannel: + raise NotImplementedError + + @overload + async def send( + self, + content: Optional[str] = ..., + *, + tts: bool = ..., + embed: 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 = ..., + embed: 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: + ... + + @overload + async def send( + self, + 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| + + Sends a message to the destination with the content given. + + The content must be a type that can convert to a string through ``str(content)``. + If the content is set to ``None`` (the default), then the ``embed`` parameter must + be provided. + + To upload a single file, the ``file`` parameter should be used with a + single :class:`~discord.File` object. To upload multiple files, the ``files`` + parameter should be used with a :class:`list` of :class:`~discord.File` objects. + **Specifying both parameters will lead to an exception**. + + To upload a single embed, the ``embed`` parameter should be used with a + 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 + ------------ + content: Optional[:class:`str`] + The content of the message to send. + tts: :class:`bool` + Indicates if the message should be sent using text-to-speech. + embed: :class:`~discord.Embed` + The rich embed for the content. + file: :class:`~discord.File` + The file to upload. + files: List[:class:`~discord.File`] + A list of files to upload. Must be a maximum of 10. + nonce: :class:`int` + The nonce to use for sending this message. If the message was successfully sent, + then the message will have a nonce with this value. + delete_after: :class:`float` + If provided, the number of seconds to wait in the background + before deleting the message we just sent. If the deletion fails, + then it is silently ignored. + allowed_mentions: :class:`~discord.AllowedMentions` + Controls the mentions being processed in this message. If this is + passed, then the object is merged with :attr:`~discord.Client.allowed_mentions`. + The merging behaviour only overrides attributes that have been explicitly passed + to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`. + If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions` + are used instead. + + .. versionadded:: 1.4 + + 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 + :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` + attribute of ``allowed_mentions`` or by setting ``mention_author``. + + .. versionadded:: 1.6 + + mention_author: Optional[:class:`bool`] + If set, overrides the :attr:`~discord.AllowedMentions.replied_user` attribute of ``allowed_mentions``. + + .. versionadded:: 1.6 + view: :class:`discord.ui.View` + 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 + + Raises + -------- + ~discord.HTTPException + Sending the message failed. + ~discord.Forbidden + You do not have the proper permissions to send the message. + ~discord.InvalidArgument + The ``files`` list is not of the appropriate size, + you specified both ``file`` and ``files``, + or you specified both ``embed`` and ``embeds``, + or the ``reference`` object is not a :class:`~discord.Message`, + :class:`~discord.MessageReference` or :class:`~discord.PartialMessage`. + + Returns + --------- + :class:`~discord.Message` + The message that was sent. + """ + + channel = await self._get_channel() + state = self._state + 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: + 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 state.allowed_mentions is not None: + allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict() + else: + allowed_mentions = allowed_mentions.to_dict() + else: + allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict() + + if mention_author is not None: + allowed_mentions = allowed_mentions or AllowedMentions().to_dict() + allowed_mentions['replied_user'] = bool(mention_author) + + if reference is not None: + try: + reference = reference.to_message_reference_dict() + except AttributeError: + raise InvalidArgument('reference parameter must be Message, MessageReference, or PartialMessage') from None + + if view: + if not hasattr(view, '__discord_ui_view__'): + raise InvalidArgument(f'view parameter must be View not {view.__class__!r}') + + components = view.to_components() + else: + components = None + + if file is not None and files is not None: + raise InvalidArgument('cannot pass both file and files parameter to send()') + + if file is not None: + if not isinstance(file, File): + raise InvalidArgument('file parameter must be File') + + try: + data = await state.http.send_files( + channel.id, + files=[file], + allowed_mentions=allowed_mentions, + content=content, + tts=tts, + embed=embed, + embeds=embeds, + nonce=nonce, + message_reference=reference, + stickers=stickers, + components=components, + ) + finally: + file.close() + + elif files is not None: + if len(files) > 10: + raise InvalidArgument('files parameter must be a list of up to 10 elements') + elif not all(isinstance(file, File) for file in files): + raise InvalidArgument('files parameter must be a list of File') + + try: + data = await state.http.send_files( + channel.id, + files=files, + content=content, + tts=tts, + embed=embed, + embeds=embeds, + nonce=nonce, + allowed_mentions=allowed_mentions, + message_reference=reference, + stickers=stickers, + components=components, + ) + finally: + for f in files: + f.close() + else: + data = await state.http.send_message( + channel.id, + 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) + if view: + state.store_view(view, ret.id) + + if delete_after is not None: + await ret.delete(delay=delete_after) + return ret + + async def trigger_typing(self) -> None: + """|coro| + + Triggers a *typing* indicator to the destination. + + *Typing* indicator will go away after 10 seconds, or after a message is sent. + """ + + channel = await self._get_channel() + await self._state.http.send_typing(channel.id) + + def typing(self) -> Typing: + """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. + + .. note:: + + This is both a regular context manager and an async context manager. + This means that both ``with`` and ``async with`` work with this. + + Example Usage: :: + + async with channel.typing(): + # simulate something heavy + await asyncio.sleep(10) + + await channel.send('done!') + + """ + return Typing(self) + + async def fetch_message(self, id: int, /) -> Message: + """|coro| + + Retrieves a single :class:`~discord.Message` from the destination. + + Parameters + ------------ + id: :class:`int` + The message ID to look for. + + Raises + -------- + ~discord.NotFound + The specified message was not found. + ~discord.Forbidden + You do not have the permissions required to get a message. + ~discord.HTTPException + Retrieving the message failed. + + Returns + -------- + :class:`~discord.Message` + The message asked for. + """ + + channel = await self._get_channel() + data = await self._state.http.get_message(channel.id, id) + return self._state.create_message(channel=channel, data=data) + + async def pins(self) -> List[Message]: + """|coro| + + Retrieves all messages that are currently pinned in the channel. + + .. note:: + + Due to a limitation with the Discord API, the :class:`.Message` + objects returned by this method do not contain complete + :attr:`.Message.reactions` data. + + Raises + ------- + ~discord.HTTPException + Retrieving the pinned messages failed. + + Returns + -------- + List[:class:`~discord.Message`] + The messages that are currently pinned. + """ + + channel = await self._get_channel() + state = self._state + data = await state.http.pins_from(channel.id) + return [state.create_message(channel=channel, data=m) for m in data] + + def can_send(self, *objects) -> bool: + """Returns a :class:`bool` indicating whether you have the permissions to send the object(s). + + Raises + ------ + TypeError + An invalid type has been passed. + + Returns + -------- + :class:`bool` + Indicates whether you have the permissions to send the object(s). + """ + mapping = { + 'Message': 'send_messages', + 'Embed': 'embed_links', + 'File': 'attach_files', + 'Emoji': 'use_external_emojis', + 'GuildSticker': 'use_external_stickers', + } + # Can't use channel = await self._get_channel() since its async + if hasattr(self, 'permissions_for'): + channel = self + elif hasattr(self, 'channel') and not type(self.channel).__name__ in ('DMChannel', 'GroupChannel'): + channel = self.channel + else: + return True # Permissions don't exist for User DMs + + + objects = (None, ) + objects # Makes sure we check for send_messages first + + for obj in objects: + try: + if obj is None: + permission = mapping['Message'] + else: + permission = mapping.get(type(obj).__name__) or mapping[obj.__name__] + + if type(obj).__name__ == 'Emoji': + if obj._to_partial().is_unicode_emoji or obj.guild_id == channel.guild.id: + continue + elif type(obj).__name__ == 'GuildSticker': + if obj.guild_id == channel.guild.id: + continue + + except (KeyError, AttributeError): + raise TypeError(f'The object {obj} is of an invalid type.') + + if not getattr(channel.permissions_for(channel.guild.me), permission): + return False + + return True + + 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. + + You must have :attr:`~discord.Permissions.read_message_history` permissions to use this. + + Examples + --------- + + Usage :: + + counter = 0 + async for message in channel.history(limit=200): + if message.author == client.user: + counter += 1 + + Flattening into a list: :: + + messages = await channel.history(limit=123).flatten() + # messages is now a list of Message... + + All parameters are optional. + + Parameters + ----------- + limit: Optional[:class:`int`] + The number of messages to retrieve. + If ``None``, retrieves every message in the channel. Note, however, + that this would make it a slow operation. + before: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve messages before this date or message. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + after: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve messages after this date or message. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + around: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve messages around this date or message. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + When using this argument, the maximum limit is 101. Note that if the limit is an + even number then this will return at most limit + 1 messages. + oldest_first: Optional[:class:`bool`] + If set to ``True``, return messages in oldest->newest order. Defaults to ``True`` if + ``after`` is specified, otherwise ``False``. + + Raises + ------ + ~discord.Forbidden + You do not have permissions to get channel message history. + ~discord.HTTPException + The request to get message history failed. + + Yields + ------- + :class:`~discord.Message` + The message with the message data parsed. + """ + return HistoryIterator(self, limit=limit, before=before, after=after, around=around, oldest_first=oldest_first) + + +class Connectable(Protocol): + """An ABC that details the common operations on a channel that can + connect to a voice server. + + The following implement this ABC: + + - :class:`~discord.VoiceChannel` + - :class:`~discord.StageChannel` + + Note + ---- + This ABC is not decorated with :func:`typing.runtime_checkable`, so will fail :func:`isinstance`/:func:`issubclass` + checks. + """ + + __slots__ = () + _state: ConnectionState + + def _get_voice_client_key(self) -> Tuple[int, str]: + raise NotImplementedError + + def _get_voice_state_pair(self) -> Tuple[int, int]: + raise NotImplementedError + + async def connect( + self, + *, + timeout: float = 60.0, + reconnect: bool = True, + cls: Callable[[Client, Connectable], T] = VoiceClient, + ) -> T: + """|coro| + + Connects to voice and creates a :class:`VoiceClient` to establish + your connection to the voice server. + + This requires :attr:`Intents.voice_states`. + + Parameters + ----------- + timeout: :class:`float` + The timeout in seconds to wait for the voice endpoint. + reconnect: :class:`bool` + Whether the bot should automatically attempt + a reconnect if a part of the handshake fails + or the gateway goes down. + cls: Type[:class:`VoiceProtocol`] + A type that subclasses :class:`~discord.VoiceProtocol` to connect with. + Defaults to :class:`~discord.VoiceClient`. + + Raises + ------- + asyncio.TimeoutError + Could not connect to the voice channel in time. + ~discord.ClientException + You are already connected to a voice channel. + ~discord.opus.OpusNotLoaded + The opus library has not been loaded. + + Returns + -------- + :class:`~discord.VoiceProtocol` + A voice client that is fully connected to the voice server. + """ + + key_id, _ = self._get_voice_client_key() + state = self._state + + if state._get_voice_client(key_id): + raise ClientException('Already connected to a voice channel.') + + client = state._get_client() + voice = cls(client, self) + + if not isinstance(voice, VoiceProtocol): + raise TypeError('Type must meet VoiceProtocol abstract base class.') + + state._add_voice_client(key_id, voice) + + try: + await voice.connect(timeout=timeout, reconnect=reconnect) + except asyncio.TimeoutError: + try: + await voice.disconnect(force=True) + except Exception: + # we don't care if disconnect failed because connection failed + pass + raise # re-raise + + return voice + +class Mentionable: + # TODO: documentation, methods if needed + pass diff --git a/build/lib/discord/activity.py b/build/lib/discord/activity.py new file mode 100644 index 0000000000..98dd4ac5ae --- /dev/null +++ b/build/lib/discord/activity.py @@ -0,0 +1,843 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import datetime +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union, overload + +from .asset import Asset +from .enums import ActivityType, try_enum +from .colour import Colour +from .partial_emoji import PartialEmoji +from .utils import _get_as_snowflake + +__all__ = ( + 'BaseActivity', + 'Activity', + 'Streaming', + 'Game', + 'Spotify', + 'CustomActivity', +) + +"""If curious, this is the current schema for an activity. + +It's fairly long so I will document it here: + +All keys are optional. + +state: str (max: 128), +details: str (max: 128) +timestamps: dict + start: int (min: 1) + end: int (min: 1) +assets: dict + large_image: str (max: 32) + large_text: str (max: 128) + small_image: str (max: 32) + small_text: str (max: 128) +party: dict + id: str (max: 128), + size: List[int] (max-length: 2) + elem: int (min: 1) +secrets: dict + match: str (max: 128) + join: str (max: 128) + spectate: str (max: 128) +instance: bool +application_id: str +name: str (max: 128) +url: str +type: int +sync_id: str +session_id: str +flags: int +buttons: list[dict] + label: str (max: 32) + url: str (max: 512) + +There are also activity flags which are mostly uninteresting for the library atm. + +t.ActivityFlags = { + INSTANCE: 1, + JOIN: 2, + SPECTATE: 4, + JOIN_REQUEST: 8, + SYNC: 16, + PLAY: 32 +} +""" + +if TYPE_CHECKING: + from .types.activity import ( + Activity as ActivityPayload, + ActivityTimestamps, + ActivityParty, + ActivityAssets, + ActivityButton, + ) + + +class BaseActivity: + """The base activity that all user-settable activities inherit from. + A user-settable activity is one that can be used in :meth:`Client.change_presence`. + + The following types currently count as user-settable: + + - :class:`Activity` + - :class:`Game` + - :class:`Streaming` + - :class:`CustomActivity` + + Note that although these types are considered user-settable by the library, + Discord typically ignores certain combinations of activity depending on + what is currently set. This behaviour may change in the future so there are + no guarantees on whether Discord will actually let you set these types. + + .. versionadded:: 1.3 + """ + + __slots__ = ('_created_at',) + + def __init__(self, **kwargs): + self._created_at: Optional[float] = kwargs.pop('created_at', None) + + @property + def created_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC. + + .. versionadded:: 1.3 + """ + if self._created_at is not None: + return datetime.datetime.fromtimestamp(self._created_at / 1000, tz=datetime.timezone.utc) + + def to_dict(self) -> ActivityPayload: + raise NotImplementedError + + +class Activity(BaseActivity): + """Represents an activity in Discord. + + This could be an activity such as streaming, playing, listening + or watching. + + For memory optimisation purposes, some activities are offered in slimmed + down versions: + + - :class:`Game` + - :class:`Streaming` + + Attributes + ------------ + application_id: Optional[:class:`int`] + The application ID of the game. + name: Optional[:class:`str`] + The name of the activity. + url: Optional[:class:`str`] + A stream URL that the activity could be doing. + type: :class:`ActivityType` + The type of activity currently being done. + state: Optional[:class:`str`] + The user's current state. For example, "In Game". + details: Optional[:class:`str`] + The detail of the user's current activity. + timestamps: :class:`dict` + A dictionary of timestamps. It contains the following optional keys: + + - ``start``: Corresponds to when the user started doing the + activity in milliseconds since Unix epoch. + - ``end``: Corresponds to when the user will finish doing the + activity in milliseconds since Unix epoch. + + assets: :class:`dict` + A dictionary representing the images and their hover text of an activity. + It contains the following optional keys: + + - ``large_image``: A string representing the ID for the large image asset. + - ``large_text``: A string representing the text when hovering over the large image asset. + - ``small_image``: A string representing the ID for the small image asset. + - ``small_text``: A string representing the text when hovering over the small image asset. + + party: :class:`dict` + A dictionary representing the activity party. It contains the following optional keys: + + - ``id``: A string representing the party ID. + - ``size``: A list of up to two integer elements denoting (current_size, maximum_size). + buttons: List[:class:`dict`] + An list of dictionaries representing custom buttons shown in a rich presence. + Each dictionary contains the following keys: + + - ``label``: A string representing the text shown on the button. + - ``url``: A string representing the URL opened upon clicking the button. + + .. versionadded:: 2.0 + + emoji: Optional[:class:`PartialEmoji`] + The emoji that belongs to this activity. + """ + + __slots__ = ( + 'state', + 'details', + '_created_at', + 'timestamps', + 'assets', + 'party', + 'flags', + 'sync_id', + 'session_id', + 'type', + 'name', + 'url', + 'application_id', + 'emoji', + 'buttons', + ) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.state: Optional[str] = kwargs.pop('state', None) + self.details: Optional[str] = kwargs.pop('details', None) + self.timestamps: ActivityTimestamps = kwargs.pop('timestamps', {}) + self.assets: ActivityAssets = kwargs.pop('assets', {}) + self.party: ActivityParty = kwargs.pop('party', {}) + self.application_id: Optional[int] = _get_as_snowflake(kwargs, 'application_id') + self.name: Optional[str] = kwargs.pop('name', None) + self.url: Optional[str] = kwargs.pop('url', None) + self.flags: int = kwargs.pop('flags', 0) + self.sync_id: Optional[str] = kwargs.pop('sync_id', None) + self.session_id: Optional[str] = kwargs.pop('session_id', None) + self.buttons: List[ActivityButton] = kwargs.pop('buttons', []) + + activity_type = kwargs.pop('type', -1) + self.type: ActivityType = ( + activity_type if isinstance(activity_type, ActivityType) else try_enum(ActivityType, activity_type) + ) + + emoji = kwargs.pop('emoji', None) + self.emoji: Optional[PartialEmoji] = PartialEmoji.from_dict(emoji) if emoji is not None else None + + def __repr__(self) -> str: + attrs = ( + ('type', self.type), + ('name', self.name), + ('url', self.url), + ('details', self.details), + ('application_id', self.application_id), + ('session_id', self.session_id), + ('emoji', self.emoji), + ) + inner = ' '.join('%s=%r' % t for t in attrs) + return f'' + + def to_dict(self) -> Dict[str, Any]: + ret: Dict[str, Any] = {} + for attr in self.__slots__: + value = getattr(self, attr, None) + if value is None: + continue + + if isinstance(value, dict) and len(value) == 0: + continue + + ret[attr] = value + ret['type'] = int(self.type) + if self.emoji: + ret['emoji'] = self.emoji.to_dict() + return ret + + @property + def start(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable.""" + try: + timestamp = self.timestamps['start'] / 1000 + except KeyError: + return None + else: + return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) + + @property + def end(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable.""" + try: + timestamp = self.timestamps['end'] / 1000 + except KeyError: + return None + else: + return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) + + @property + def large_image_url(self) -> Optional[str]: + """Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity if applicable.""" + if self.application_id is None: + return None + + try: + large_image = self.assets['large_image'] + except KeyError: + return None + else: + return Asset.BASE + f'/app-assets/{self.application_id}/{large_image}.png' + + @property + def small_image_url(self) -> Optional[str]: + """Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity if applicable.""" + if self.application_id is None: + return None + + try: + small_image = self.assets['small_image'] + except KeyError: + return None + else: + return Asset.BASE + f'/app-assets/{self.application_id}/{small_image}.png' + + @property + def large_image_text(self) -> Optional[str]: + """Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable.""" + return self.assets.get('large_text', None) + + @property + def small_image_text(self) -> Optional[str]: + """Optional[:class:`str`]: Returns the small image asset hover text of this activity if applicable.""" + return self.assets.get('small_text', None) + + +class Game(BaseActivity): + """A slimmed down version of :class:`Activity` that represents a Discord game. + + This is typically displayed via **Playing** on the official Discord client. + + .. container:: operations + + .. describe:: x == y + + Checks if two games are equal. + + .. describe:: x != y + + Checks if two games are not equal. + + .. describe:: hash(x) + + Returns the game's hash. + + .. describe:: str(x) + + Returns the game's name. + + Parameters + ----------- + name: :class:`str` + The game's name. + + Attributes + ----------- + name: :class:`str` + The game's name. + """ + + __slots__ = ('name', '_end', '_start') + + def __init__(self, name: str, **extra): + super().__init__(**extra) + self.name: str = name + + try: + timestamps: ActivityTimestamps = extra['timestamps'] + except KeyError: + self._start = 0 + self._end = 0 + else: + self._start = timestamps.get('start', 0) + self._end = timestamps.get('end', 0) + + @property + def type(self) -> ActivityType: + """:class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`. + + It always returns :attr:`ActivityType.playing`. + """ + return ActivityType.playing + + @property + def start(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable.""" + if self._start: + return datetime.datetime.fromtimestamp(self._start / 1000, tz=datetime.timezone.utc) + return None + + @property + def end(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable.""" + if self._end: + return datetime.datetime.fromtimestamp(self._end / 1000, tz=datetime.timezone.utc) + return None + + def __str__(self) -> str: + return str(self.name) + + def __repr__(self) -> str: + return f'' + + def to_dict(self) -> Dict[str, Any]: + timestamps: Dict[str, Any] = {} + if self._start: + timestamps['start'] = self._start + + if self._end: + timestamps['end'] = self._end + + # fmt: off + return { + 'type': ActivityType.playing.value, + 'name': str(self.name), + 'timestamps': timestamps + } + # fmt: on + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Game) and other.name == self.name + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return hash(self.name) + + +class Streaming(BaseActivity): + """A slimmed down version of :class:`Activity` that represents a Discord streaming status. + + This is typically displayed via **Streaming** on the official Discord client. + + .. container:: operations + + .. describe:: x == y + + Checks if two streams are equal. + + .. describe:: x != y + + Checks if two streams are not equal. + + .. describe:: hash(x) + + Returns the stream's hash. + + .. describe:: str(x) + + Returns the stream's name. + + Attributes + ----------- + platform: Optional[:class:`str`] + Where the user is streaming from (ie. YouTube, Twitch). + + .. versionadded:: 1.3 + + name: Optional[:class:`str`] + The stream's name. + details: Optional[:class:`str`] + An alias for :attr:`name` + game: Optional[:class:`str`] + The game being streamed. + + .. versionadded:: 1.3 + + url: :class:`str` + The stream's URL. + assets: :class:`dict` + A dictionary comprising of similar keys than those in :attr:`Activity.assets`. + """ + + __slots__ = ('platform', 'name', 'game', 'url', 'details', 'assets') + + def __init__(self, *, name: Optional[str], url: str, **extra: Any): + super().__init__(**extra) + self.platform: Optional[str] = name + self.name: Optional[str] = extra.pop('details', name) + self.game: Optional[str] = extra.pop('state', None) + self.url: str = url + self.details: Optional[str] = extra.pop('details', self.name) # compatibility + self.assets: ActivityAssets = extra.pop('assets', {}) + + @property + def type(self) -> ActivityType: + """:class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`. + + It always returns :attr:`ActivityType.streaming`. + """ + return ActivityType.streaming + + def __str__(self) -> str: + return str(self.name) + + def __repr__(self) -> str: + return f'' + + @property + def twitch_name(self): + """Optional[:class:`str`]: If provided, the twitch name of the user streaming. + + This corresponds to the ``large_image`` key of the :attr:`Streaming.assets` + dictionary if it starts with ``twitch:``. Typically set by the Discord client. + """ + + try: + name = self.assets['large_image'] + except KeyError: + return None + else: + return name[7:] if name[:7] == 'twitch:' else None + + def to_dict(self) -> Dict[str, Any]: + # fmt: off + ret: Dict[str, Any] = { + 'type': ActivityType.streaming.value, + 'name': str(self.name), + 'url': str(self.url), + 'assets': self.assets + } + # fmt: on + if self.details: + ret['details'] = self.details + return ret + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Streaming) and other.name == self.name and other.url == self.url + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return hash(self.name) + + +class Spotify: + """Represents a Spotify listening activity from Discord. This is a special case of + :class:`Activity` that makes it easier to work with the Spotify integration. + + .. container:: operations + + .. describe:: x == y + + Checks if two activities are equal. + + .. describe:: x != y + + Checks if two activities are not equal. + + .. describe:: hash(x) + + Returns the activity's hash. + + .. describe:: str(x) + + Returns the string 'Spotify'. + """ + + __slots__ = ('_state', '_details', '_timestamps', '_assets', '_party', '_sync_id', '_session_id', '_created_at') + + def __init__(self, **data): + self._state: str = data.pop('state', '') + self._details: str = data.pop('details', '') + self._timestamps: Dict[str, int] = data.pop('timestamps', {}) + self._assets: ActivityAssets = data.pop('assets', {}) + self._party: ActivityParty = data.pop('party', {}) + self._sync_id: str = data.pop('sync_id') + self._session_id: str = data.pop('session_id') + self._created_at: Optional[float] = data.pop('created_at', None) + + @property + def type(self) -> ActivityType: + """:class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`. + + It always returns :attr:`ActivityType.listening`. + """ + return ActivityType.listening + + @property + def created_at(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: When the user started listening in UTC. + + .. versionadded:: 1.3 + """ + if self._created_at is not None: + return datetime.datetime.fromtimestamp(self._created_at / 1000, tz=datetime.timezone.utc) + + @property + def colour(self) -> Colour: + """:class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`. + + There is an alias for this named :attr:`color`""" + return Colour(0x1DB954) + + @property + def color(self) -> Colour: + """:class:`Colour`: Returns the Spotify integration colour, as a :class:`Colour`. + + There is an alias for this named :attr:`colour`""" + return self.colour + + def to_dict(self) -> Dict[str, Any]: + return { + 'flags': 48, # SYNC | PLAY + 'name': 'Spotify', + 'assets': self._assets, + 'party': self._party, + 'sync_id': self._sync_id, + 'session_id': self._session_id, + 'timestamps': self._timestamps, + 'details': self._details, + 'state': self._state, + } + + @property + def name(self) -> str: + """:class:`str`: The activity's name. This will always return "Spotify".""" + return 'Spotify' + + def __eq__(self, other: Any) -> bool: + return ( + isinstance(other, Spotify) + and other._session_id == self._session_id + and other._sync_id == self._sync_id + and other.start == self.start + ) + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return hash(self._session_id) + + def __str__(self) -> str: + return 'Spotify' + + def __repr__(self) -> str: + return f'' + + @property + def title(self) -> str: + """:class:`str`: The title of the song being played.""" + return self._details + + @property + def artists(self) -> List[str]: + """List[:class:`str`]: The artists of the song being played.""" + return self._state.split('; ') + + @property + def artist(self) -> str: + """:class:`str`: The artist of the song being played. + + This does not attempt to split the artist information into + multiple artists. Useful if there's only a single artist. + """ + return self._state + + @property + def album(self) -> str: + """:class:`str`: The album that the song being played belongs to.""" + return self._assets.get('large_text', '') + + @property + def album_cover_url(self) -> str: + """:class:`str`: The album cover image URL from Spotify's CDN.""" + large_image = self._assets.get('large_image', '') + if large_image[:8] != 'spotify:': + return '' + album_image_id = large_image[8:] + return 'https://i.scdn.co/image/' + album_image_id + + @property + def track_id(self) -> str: + """:class:`str`: The track ID used by Spotify to identify this song.""" + return self._sync_id + + @property + 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.""" + return datetime.datetime.fromtimestamp(self._timestamps['start'] / 1000, tz=datetime.timezone.utc) + + @property + def end(self) -> datetime.datetime: + """:class:`datetime.datetime`: When the user will stop playing this song in UTC.""" + return datetime.datetime.fromtimestamp(self._timestamps['end'] / 1000, tz=datetime.timezone.utc) + + @property + def duration(self) -> datetime.timedelta: + """:class:`datetime.timedelta`: The duration of the song being played.""" + return self.end - self.start + + @property + def party_id(self) -> str: + """:class:`str`: The party ID of the listening party.""" + return self._party.get('id', '') + + +class CustomActivity(BaseActivity): + """Represents a Custom activity from Discord. + + .. container:: operations + + .. describe:: x == y + + Checks if two activities are equal. + + .. describe:: x != y + + Checks if two activities are not equal. + + .. describe:: hash(x) + + Returns the activity's hash. + + .. describe:: str(x) + + Returns the custom status text. + + .. versionadded:: 1.3 + + Attributes + ----------- + name: Optional[:class:`str`] + The custom activity's name. + emoji: Optional[:class:`PartialEmoji`] + The emoji to pass to the activity, if any. + """ + + __slots__ = ('name', 'emoji', 'state') + + def __init__(self, name: Optional[str], *, emoji: Optional[PartialEmoji] = None, **extra: Any): + super().__init__(**extra) + self.name: Optional[str] = name + self.state: Optional[str] = extra.pop('state', None) + if self.name == 'Custom Status': + self.name = self.state + + self.emoji: Optional[PartialEmoji] + if emoji is None: + self.emoji = emoji + elif isinstance(emoji, dict): + self.emoji = PartialEmoji.from_dict(emoji) + elif isinstance(emoji, str): + self.emoji = PartialEmoji(name=emoji) + elif isinstance(emoji, PartialEmoji): + self.emoji = emoji + else: + raise TypeError(f'Expected str, PartialEmoji, or None, received {type(emoji)!r} instead.') + + @property + def type(self) -> ActivityType: + """:class:`ActivityType`: Returns the activity's type. This is for compatibility with :class:`Activity`. + + It always returns :attr:`ActivityType.custom`. + """ + return ActivityType.custom + + def to_dict(self) -> Dict[str, Any]: + if self.name == self.state: + o = { + 'type': ActivityType.custom.value, + 'state': self.name, + 'name': 'Custom Status', + } + else: + o = { + 'type': ActivityType.custom.value, + 'name': self.name, + } + + if self.emoji: + o['emoji'] = self.emoji.to_dict() + return o + + def __eq__(self, other: Any) -> bool: + return isinstance(other, CustomActivity) and other.name == self.name and other.emoji == self.emoji + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return hash((self.name, str(self.emoji))) + + def __str__(self) -> str: + if self.emoji: + if self.name: + return f'{self.emoji} {self.name}' + return str(self.emoji) + else: + return str(self.name) + + def __repr__(self) -> str: + return f'' + + +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: + return None + + game_type = try_enum(ActivityType, data.get('type', -1)) + if game_type is ActivityType.playing: + if 'application_id' in data or 'session_id' in data: + return Activity(**data) + return Game(**data) + elif game_type is ActivityType.custom: + try: + name = data.pop('name') + except KeyError: + return Activity(**data) + else: + # we removed the name key from data already + return CustomActivity(name=name, **data) # type: ignore + elif game_type is ActivityType.streaming: + if 'url' in data: + # the url won't be None here + return Streaming(**data) # type: ignore + return Activity(**data) + elif game_type is ActivityType.listening and 'sync_id' in data and 'session_id' in data: + return Spotify(**data) + return Activity(**data) diff --git a/build/lib/discord/appinfo.py b/build/lib/discord/appinfo.py new file mode 100644 index 0000000000..b9d208439a --- /dev/null +++ b/build/lib/discord/appinfo.py @@ -0,0 +1,248 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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, TYPE_CHECKING, Optional + +from . import utils +from .asset import Asset + +if TYPE_CHECKING: + from .guild import Guild + from .types.appinfo import ( + AppInfo as AppInfoPayload, + PartialAppInfo as PartialAppInfoPayload, + Team as TeamPayload, + ) + from .user import User + from .state import ConnectionState + +__all__ = ( + 'AppInfo', + 'PartialAppInfo', +) + + +class AppInfo: + """Represents the application info for the bot provided by Discord. + + + Attributes + ------------- + id: :class:`int` + The application ID. + name: :class:`str` + The application name. + owner: :class:`User` + The application owner. + team: Optional[:class:`Team`] + The application's team. + + .. versionadded:: 1.3 + + description: :class:`str` + The application description. + bot_public: :class:`bool` + Whether the bot can be invited by anyone or if it is locked + to the application owner. + bot_require_code_grant: :class:`bool` + Whether the bot requires the completion of the full oauth2 code + grant flow to join. + rpc_origins: Optional[List[:class:`str`]] + A list of RPC origin URLs, if RPC is enabled. + summary: :class:`str` + If this application is a game sold on Discord, + this field will be the summary field for the store page of its primary SKU. + + .. versionadded:: 1.3 + + verify_key: :class:`str` + The hex encoded key for verification in interactions and the + GameSDK's `GetTicket `_. + + .. versionadded:: 1.3 + + guild_id: Optional[:class:`int`] + If this application is a game sold on Discord, + this field will be the guild to which it has been linked to. + + .. versionadded:: 1.3 + + primary_sku_id: Optional[:class:`int`] + If this application is a game sold on Discord, + this field will be the id of the "Game SKU" that is created, + if it exists. + + .. versionadded:: 1.3 + + slug: Optional[:class:`str`] + If this application is a game sold on Discord, + this field will be the URL slug that links to the store page. + + .. versionadded:: 1.3 + + terms_of_service_url: Optional[:class:`str`] + The application's terms of service URL, if set. + + .. versionadded:: 2.0 + + privacy_policy_url: Optional[:class:`str`] + The application's privacy policy URL, if set. + + .. versionadded:: 2.0 + """ + + __slots__ = ( + '_state', + 'description', + 'id', + 'name', + 'rpc_origins', + 'bot_public', + 'bot_require_code_grant', + 'owner', + '_icon', + 'summary', + 'verify_key', + 'team', + 'guild_id', + 'primary_sku_id', + 'slug', + '_cover_image', + 'terms_of_service_url', + 'privacy_policy_url', + ) + + def __init__(self, state: ConnectionState, data: AppInfoPayload): + from .team import Team + + self._state: ConnectionState = state + self.id: int = int(data['id']) + self.name: str = data['name'] + self.description: str = data['description'] + self._icon: Optional[str] = data['icon'] + self.rpc_origins: List[str] = data['rpc_origins'] + self.bot_public: bool = data['bot_public'] + self.bot_require_code_grant: bool = data['bot_require_code_grant'] + self.owner: User = state.create_user(data['owner']) + + team: Optional[TeamPayload] = data.get('team') + self.team: Optional[Team] = Team(state, team) if team else None + + self.summary: str = data['summary'] + self.verify_key: str = data['verify_key'] + + self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id') + + self.primary_sku_id: Optional[int] = utils._get_as_snowflake(data, 'primary_sku_id') + self.slug: Optional[str] = data.get('slug') + self._cover_image: Optional[str] = data.get('cover_image') + self.terms_of_service_url: Optional[str] = data.get('terms_of_service_url') + self.privacy_policy_url: Optional[str] = data.get('privacy_policy_url') + + def __repr__(self) -> str: + return ( + f'<{self.__class__.__name__} id={self.id} name={self.name!r} ' + f'description={self.description!r} public={self.bot_public} ' + f'owner={self.owner!r}>' + ) + + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`.Asset`]: Retrieves the application's icon asset, if any.""" + if self._icon is None: + return None + return Asset._from_icon(self._state, self.id, self._icon, path='app') + + @property + def cover_image(self) -> Optional[Asset]: + """Optional[:class:`.Asset`]: Retrieves the cover image on a store embed, if any. + + This is only available if the application is a game sold on Discord. + """ + if self._cover_image is None: + return None + return Asset._from_cover_image(self._state, self.id, self._cover_image) + + @property + def guild(self) -> Optional[Guild]: + """Optional[:class:`Guild`]: If this application is a game sold on Discord, + this field will be the guild to which it has been linked + + .. versionadded:: 1.3 + """ + return self._state._get_guild(self.guild_id) + +class PartialAppInfo: + """Represents a partial AppInfo given by :func:`~discord.abc.GuildChannel.create_invite` + + .. versionadded:: 2.0 + + Attributes + ------------- + id: :class:`int` + The application ID. + name: :class:`str` + The application name. + description: :class:`str` + The application description. + rpc_origins: Optional[List[:class:`str`]] + A list of RPC origin URLs, if RPC is enabled. + summary: :class:`str` + If this application is a game sold on Discord, + this field will be the summary field for the store page of its primary SKU. + verify_key: :class:`str` + The hex encoded key for verification in interactions and the + GameSDK's `GetTicket `_. + terms_of_service_url: Optional[:class:`str`] + The application's terms of service URL, if set. + privacy_policy_url: Optional[:class:`str`] + The application's privacy policy URL, if set. + """ + + __slots__ = ('_state', 'id', 'name', 'description', 'rpc_origins', 'summary', 'verify_key', 'terms_of_service_url', 'privacy_policy_url', '_icon') + + def __init__(self, *, state: ConnectionState, data: PartialAppInfoPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self.name: str = data['name'] + self._icon: Optional[str] = data.get('icon') + self.description: str = data['description'] + self.rpc_origins: Optional[List[str]] = data.get('rpc_origins') + self.summary: str = data['summary'] + self.verify_key: str = data['verify_key'] + self.terms_of_service_url: Optional[str] = data.get('terms_of_service_url') + self.privacy_policy_url: Optional[str] = data.get('privacy_policy_url') + + def __repr__(self) -> str: + return f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r}>' + + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`.Asset`]: Retrieves the application's icon asset, if any.""" + if self._icon is None: + return None + return Asset._from_icon(self._state, self.id, self._icon, path='app') diff --git a/build/lib/discord/asset.py b/build/lib/discord/asset.py new file mode 100644 index 0000000000..dade1397cd --- /dev/null +++ b/build/lib/discord/asset.py @@ -0,0 +1,417 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import io +import os +from typing import Any, Literal, Optional, TYPE_CHECKING, Tuple, Union +from .errors import DiscordException +from .errors import InvalidArgument +from . import utils + +import yarl + +__all__ = ( + 'Asset', +) + +if TYPE_CHECKING: + ValidStaticFormatTypes = Literal['webp', 'jpeg', 'jpg', 'png'] + ValidAssetFormatTypes = Literal['webp', 'jpeg', 'jpg', 'png', 'gif'] + +VALID_STATIC_FORMATS = frozenset({"jpeg", "jpg", "webp", "png"}) +VALID_ASSET_FORMATS = VALID_STATIC_FORMATS | {"gif"} + + +MISSING = utils.MISSING + +class AssetMixin: + url: str + _state: Optional[Any] + + async def read(self) -> bytes: + """|coro| + + Retrieves the content of this asset as a :class:`bytes` object. + + Raises + ------ + DiscordException + There was no internal connection state. + HTTPException + Downloading the asset failed. + NotFound + The asset was deleted. + + Returns + ------- + :class:`bytes` + The content of the asset. + """ + if self._state is None: + raise DiscordException('Invalid state (no ConnectionState provided)') + + return await self._state.http.get_from_cdn(self.url) + + async def save(self, fp: Union[str, bytes, os.PathLike, io.BufferedIOBase], *, seek_begin: bool = True) -> int: + """|coro| + + Saves this asset into a file-like object. + + Parameters + ---------- + fp: Union[:class:`io.BufferedIOBase`, :class:`os.PathLike`] + The file-like object to save this attachment to or the filename + to use. If a filename is passed then a file is created with that + filename and used instead. + seek_begin: :class:`bool` + Whether to seek to the beginning of the file after saving is + successfully done. + + Raises + ------ + DiscordException + There was no internal connection state. + HTTPException + Downloading the asset failed. + NotFound + The asset was deleted. + + Returns + -------- + :class:`int` + The number of bytes written. + """ + + data = await self.read() + if isinstance(fp, io.BufferedIOBase): + written = fp.write(data) + if seek_begin: + fp.seek(0) + return written + else: + with open(fp, 'wb') as f: + return f.write(data) + + +class Asset(AssetMixin): + """Represents a CDN asset on Discord. + + .. container:: operations + + .. describe:: str(x) + + Returns the URL of the CDN asset. + + .. describe:: len(x) + + Returns the length of the CDN asset's URL. + + .. describe:: x == y + + Checks if the asset is equal to another asset. + + .. describe:: x != y + + Checks if the asset is not equal to another asset. + + .. describe:: hash(x) + + Returns the hash of the asset. + """ + + __slots__: Tuple[str, ...] = ( + '_state', + '_url', + '_animated', + '_key', + ) + + BASE = 'https://cdn.discordapp.com' + + def __init__(self, state, *, url: str, key: str, animated: bool = False): + self._state = state + self._url = url + self._animated = animated + self._key = key + + @classmethod + def _from_default_avatar(cls, state, index: int) -> Asset: + return cls( + state, + url=f'{cls.BASE}/embed/avatars/{index}.png', + key=str(index), + animated=False, + ) + + @classmethod + def _from_avatar(cls, state, user_id: int, avatar: str) -> Asset: + animated = avatar.startswith('a_') + format = 'gif' if animated else 'png' + return cls( + state, + url=f'{cls.BASE}/avatars/{user_id}/{avatar}.{format}?size=1024', + key=avatar, + 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 + def _from_icon(cls, state, object_id: int, icon_hash: str, path: str) -> Asset: + return cls( + state, + url=f'{cls.BASE}/{path}-icons/{object_id}/{icon_hash}.png?size=1024', + key=icon_hash, + animated=False, + ) + + @classmethod + def _from_cover_image(cls, state, object_id: int, cover_image_hash: str) -> Asset: + return cls( + state, + url=f'{cls.BASE}/app-assets/{object_id}/store/{cover_image_hash}.png?size=1024', + key=cover_image_hash, + animated=False, + ) + + @classmethod + def _from_guild_image(cls, state, guild_id: int, image: str, path: str) -> Asset: + return cls( + state, + url=f'{cls.BASE}/{path}/{guild_id}/{image}.png?size=1024', + key=image, + animated=False, + ) + + @classmethod + def _from_guild_icon(cls, state, guild_id: int, icon_hash: str) -> Asset: + animated = icon_hash.startswith('a_') + format = 'gif' if animated else 'png' + return cls( + state, + url=f'{cls.BASE}/icons/{guild_id}/{icon_hash}.{format}?size=1024', + key=icon_hash, + animated=animated, + ) + + @classmethod + def _from_sticker_banner(cls, state, banner: int) -> Asset: + return cls( + state, + url=f'{cls.BASE}/app-assets/710982414301790216/store/{banner}.png', + key=str(banner), + 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: + return self._url + + def __len__(self) -> int: + return len(self._url) + + def __repr__(self): + shorten = self._url.replace(self.BASE, '') + return f'' + + def __eq__(self, other): + return isinstance(other, Asset) and self._url == other._url + + def __hash__(self): + return hash(self._url) + + @property + def url(self) -> str: + """:class:`str`: Returns the underlying URL of the asset.""" + return self._url + + @property + def key(self) -> str: + """:class:`str`: Returns the identifying key of the asset.""" + return self._key + + def is_animated(self) -> bool: + """:class:`bool`: Returns whether the asset is animated.""" + return self._animated + + def replace( + self, + *, + size: int = MISSING, + format: ValidAssetFormatTypes = MISSING, + static_format: ValidStaticFormatTypes = MISSING, + ) -> Asset: + """Returns a new asset with the passed components replaced. + + Parameters + ----------- + size: :class:`int` + The new size of the asset. + format: :class:`str` + The new format to change it to. Must be either + 'webp', 'jpeg', 'jpg', 'png', or 'gif' if it's animated. + static_format: :class:`str` + The new format to change it to if the asset isn't animated. + Must be either 'webp', 'jpeg', 'jpg', or 'png'. + + Raises + ------- + InvalidArgument + An invalid size or format was passed. + + Returns + -------- + :class:`Asset` + The newly updated asset. + """ + url = yarl.URL(self._url) + path, _ = os.path.splitext(url.path) + + if format is not MISSING: + if self._animated: + if format not in VALID_ASSET_FORMATS: + raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}') + url = url.with_path(f'{path}.{format}') + elif static_format is MISSING: + if format not in VALID_STATIC_FORMATS: + raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}') + url = url.with_path(f'{path}.{format}') + + if static_format is not MISSING and not self._animated: + if static_format not in VALID_STATIC_FORMATS: + raise InvalidArgument(f'static_format must be one of {VALID_STATIC_FORMATS}') + url = url.with_path(f'{path}.{static_format}') + + if size is not MISSING: + if not utils.valid_icon_size(size): + raise InvalidArgument('size must be a power of 2 between 16 and 4096') + url = url.with_query(size=size) + else: + url = url.with_query(url.raw_query_string) + + url = str(url) + return Asset(state=self._state, url=url, key=self._key, animated=self._animated) + + def with_size(self, size: int, /) -> Asset: + """Returns a new asset with the specified size. + + Parameters + ------------ + size: :class:`int` + The new size of the asset. + + Raises + ------- + InvalidArgument + The asset had an invalid size. + + Returns + -------- + :class:`Asset` + The new updated asset. + """ + if not utils.valid_icon_size(size): + raise InvalidArgument('size must be a power of 2 between 16 and 4096') + + url = str(yarl.URL(self._url).with_query(size=size)) + return Asset(state=self._state, url=url, key=self._key, animated=self._animated) + + def with_format(self, format: ValidAssetFormatTypes, /) -> Asset: + """Returns a new asset with the specified format. + + Parameters + ------------ + format: :class:`str` + The new format of the asset. + + Raises + ------- + InvalidArgument + The asset had an invalid format. + + Returns + -------- + :class:`Asset` + The new updated asset. + """ + + if self._animated: + if format not in VALID_ASSET_FORMATS: + raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}') + else: + if format not in VALID_STATIC_FORMATS: + raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}') + + url = yarl.URL(self._url) + path, _ = os.path.splitext(url.path) + 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) + + def with_static_format(self, format: ValidStaticFormatTypes, /) -> Asset: + """Returns a new asset with the specified static format. + + This only changes the format if the underlying asset is + not animated. Otherwise, the asset is not changed. + + Parameters + ------------ + format: :class:`str` + The new static format of the asset. + + Raises + ------- + InvalidArgument + The asset had an invalid format. + + Returns + -------- + :class:`Asset` + The new updated asset. + """ + + if self._animated: + return self + return self.with_format(format) diff --git a/build/lib/discord/audit_logs.py b/build/lib/discord/audit_logs.py new file mode 100644 index 0000000000..7e09d2c21f --- /dev/null +++ b/build/lib/discord/audit_logs.py @@ -0,0 +1,525 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 TYPE_CHECKING, Any, Callable, ClassVar, Dict, Generator, List, Optional, Tuple, Type, TypeVar, Union + +from . import enums, utils +from .asset import Asset +from .colour import Colour +from .invite import Invite +from .mixins import Hashable +from .object import Object +from .permissions import PermissionOverwrite, Permissions + +__all__ = ( + 'AuditLogDiff', + 'AuditLogChanges', + 'AuditLogEntry', +) + + +if TYPE_CHECKING: + import datetime + + from . import abc + from .emoji import Emoji + from .guild import Guild + from .member import Member + from .role import Role + from .types.audit_log import ( + AuditLogChange as AuditLogChangePayload, + AuditLogEntry as AuditLogEntryPayload, + ) + from .types.channel import PermissionOverwrite as PermissionOverwritePayload + from .types.role import Role as RolePayload + from .types.snowflake import Snowflake + 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: + return Permissions(int(data)) + + +def _transform_color(entry: AuditLogEntry, data: int) -> Colour: + return Colour(data) + + +def _transform_snowflake(entry: AuditLogEntry, data: Snowflake) -> int: + return int(data) + + +def _transform_channel(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Union[abc.GuildChannel, Object]]: + if data is None: + return None + return entry.guild.get_channel(int(data)) or Object(id=data) + + +def _transform_member_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Union[Member, User, None]: + if data is None: + return None + return entry._get_member(int(data)) + +def _transform_guild_id(entry: AuditLogEntry, data: Optional[Snowflake]) -> Optional[Guild]: + if data is None: + return None + return entry._state._get_guild(data) + + +def _transform_overwrites( + entry: AuditLogEntry, data: List[PermissionOverwritePayload] +) -> List[Tuple[Object, PermissionOverwrite]]: + overwrites = [] + for elem in data: + allow = Permissions(int(elem['allow'])) + deny = Permissions(int(elem['deny'])) + ow = PermissionOverwrite.from_pair(allow, deny) + + ow_type = elem['type'] + ow_id = int(elem['id']) + target = None + if ow_type == '0': + target = entry.guild.get_role(ow_id) + elif ow_type == '1': + target = entry._get_member(ow_id) + + if target is None: + target = Object(id=ow_id) + + overwrites.append((target, ow)) + + return overwrites + + +def _transform_icon(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: + if data is None: + return None + return Asset._from_guild_icon(entry._state, entry.guild.id, data) + + +def _transform_avatar(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: + if data is None: + return None + return Asset._from_avatar(entry._state, entry._target_id, data) # type: ignore + + +def _guild_hash_transformer(path: str) -> Callable[[AuditLogEntry, Optional[str]], Optional[Asset]]: + def _transform(entry: AuditLogEntry, data: Optional[str]) -> Optional[Asset]: + if data is None: + return None + return Asset._from_guild_image(entry._state, entry.guild.id, data, path=path) + + return _transform + + +T = TypeVar('T', bound=enums.Enum) + + +def _enum_transformer(enum: Type[T]) -> Callable[[AuditLogEntry, int], T]: + def _transform(entry: AuditLogEntry, data: int) -> T: + return enums.try_enum(enum, data) + + 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: + def __len__(self) -> int: + return len(self.__dict__) + + def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + yield from self.__dict__.items() + + def __repr__(self) -> str: + values = ' '.join('%s=%r' % item for item in self.__dict__.items()) + return f'' + + if TYPE_CHECKING: + + def __getattr__(self, item: str) -> Any: + ... + + def __setattr__(self, key: str, value: Any) -> Any: + ... + + +Transformer = Callable[["AuditLogEntry", Any], Any] + + +class AuditLogChanges: + # fmt: off + TRANSFORMERS: ClassVar[Dict[str, Tuple[Optional[str], Optional[Transformer]]]] = { + 'verification_level': (None, _enum_transformer(enums.VerificationLevel)), + 'explicit_content_filter': (None, _enum_transformer(enums.ContentFilter)), + 'allow': (None, _transform_permissions), + 'deny': (None, _transform_permissions), + 'permissions': (None, _transform_permissions), + 'id': (None, _transform_snowflake), + 'color': ('colour', _transform_color), + 'owner_id': ('owner', _transform_member_id), + 'inviter_id': ('inviter', _transform_member_id), + 'channel_id': ('channel', _transform_channel), + 'afk_channel_id': ('afk_channel', _transform_channel), + 'system_channel_id': ('system_channel', _transform_channel), + 'widget_channel_id': ('widget_channel', _transform_channel), + 'rules_channel_id': ('rules_channel', _transform_channel), + 'public_updates_channel_id': ('public_updates_channel', _transform_channel), + 'permission_overwrites': ('overwrites', _transform_overwrites), + 'splash_hash': ('splash', _guild_hash_transformer('splashes')), + 'banner_hash': ('banner', _guild_hash_transformer('banners')), + 'discovery_splash_hash': ('discovery_splash', _guild_hash_transformer('discovery-splashes')), + 'icon_hash': ('icon', _transform_icon), + 'avatar_hash': ('avatar', _transform_avatar), + '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)), + 'region': (None, _enum_transformer(enums.VoiceRegion)), + 'rtc_region': (None, _enum_transformer(enums.VoiceRegion)), + 'video_quality_mode': (None, _enum_transformer(enums.VideoQualityMode)), + 'privacy_level': (None, _enum_transformer(enums.StagePrivacyLevel)), + 'format_type': (None, _enum_transformer(enums.StickerFormatType)), + 'type': (None, _transform_type), + } + # fmt: on + + def __init__(self, entry: AuditLogEntry, data: List[AuditLogChangePayload]): + self.before = AuditLogDiff() + self.after = AuditLogDiff() + + for elem in data: + attr = elem['key'] + + # special cases for role add/remove + if attr == '$add': + self._handle_role(self.before, self.after, entry, elem['new_value']) # type: ignore + continue + elif attr == '$remove': + self._handle_role(self.after, self.before, entry, elem['new_value']) # type: ignore + continue + + try: + key, transformer = self.TRANSFORMERS[attr] + except (ValueError, KeyError): + transformer = None + else: + if key: + attr = key + + transformer: Optional[Transformer] + + try: + before = elem['old_value'] + except KeyError: + before = None + else: + if transformer: + before = transformer(entry, before) + + setattr(self.before, attr, before) + + try: + after = elem['new_value'] + except KeyError: + after = None + else: + if transformer: + after = transformer(entry, after) + + setattr(self.after, attr, after) + + # add an alias + if hasattr(self.after, 'colour'): + self.after.color = self.after.colour + self.before.color = self.before.colour + if hasattr(self.after, 'expire_behavior'): + self.after.expire_behaviour = self.after.expire_behavior + self.before.expire_behaviour = self.before.expire_behavior + + def __repr__(self) -> str: + return f'' + + def _handle_role(self, first: AuditLogDiff, second: AuditLogDiff, entry: AuditLogEntry, elem: List[RolePayload]) -> None: + if not hasattr(first, 'roles'): + setattr(first, 'roles', []) + + data = [] + g: Guild = entry.guild # type: ignore + + for e in elem: + role_id = int(e['id']) + role = g.get_role(role_id) + + if role is None: + role = Object(id=role_id) + role.name = e['name'] # type: ignore + + data.append(role) + + setattr(second, 'roles', data) + + +class _AuditLogProxyMemberPrune: + delete_member_days: int + members_removed: int + + +class _AuditLogProxyMemberMoveOrMessageDelete: + channel: abc.GuildChannel + count: int + + +class _AuditLogProxyMemberDisconnect: + count: int + + +class _AuditLogProxyPinAction: + channel: abc.GuildChannel + message_id: int + + +class _AuditLogProxyStageInstanceAction: + channel: abc.GuildChannel + + +class AuditLogEntry(Hashable): + r"""Represents an Audit Log entry. + + You retrieve these via :meth:`Guild.audit_logs`. + + .. container:: operations + + .. describe:: x == y + + Checks if two entries are equal. + + .. describe:: x != y + + Checks if two entries are not equal. + + .. describe:: hash(x) + + Returns the entry's hash. + + .. versionchanged:: 1.7 + Audit log entries are now comparable and hashable. + + Attributes + ----------- + action: :class:`AuditLogAction` + The action that was done. + user: :class:`abc.User` + The user who initiated this action. Usually a :class:`Member`\, unless gone + then it's a :class:`User`. + id: :class:`int` + The entry ID. + target: Any + The target that got changed. The exact type of this depends on + the action being done. + reason: Optional[:class:`str`] + The reason this action was done. + extra: Any + Extra information that this entry has that might be useful. + For most actions, this is ``None``. However in some cases it + contains extra information. See :class:`AuditLogAction` for + which actions have this field filled out. + """ + + def __init__(self, *, users: Dict[int, User], data: AuditLogEntryPayload, guild: Guild): + self._state = guild._state + self.guild = guild + self._users = users + self._from_data(data) + + def _from_data(self, data: AuditLogEntryPayload) -> None: + self.action = enums.try_enum(enums.AuditLogAction, data['action_type']) + self.id = int(data['id']) + + # this key is technically not usually present + self.reason = data.get('reason') + self.extra = data.get('options') + + if isinstance(self.action, enums.AuditLogAction) and self.extra: + if self.action is enums.AuditLogAction.member_prune: + # member prune has two keys with useful information + self.extra: _AuditLogProxyMemberPrune = type( + '_AuditLogProxy', (), {k: int(v) for k, v in self.extra.items()} + )() + elif self.action is enums.AuditLogAction.member_move or self.action is enums.AuditLogAction.message_delete: + channel_id = int(self.extra['channel_id']) + elems = { + 'count': int(self.extra['count']), + 'channel': self.guild.get_channel(channel_id) or Object(id=channel_id), + } + self.extra: _AuditLogProxyMemberMoveOrMessageDelete = type('_AuditLogProxy', (), elems)() + elif self.action is enums.AuditLogAction.member_disconnect: + # The member disconnect action has a dict with some information + elems = { + 'count': int(self.extra['count']), + } + self.extra: _AuditLogProxyMemberDisconnect = type('_AuditLogProxy', (), elems)() + elif self.action.name.endswith('pin'): + # the pin actions have a dict with some information + channel_id = int(self.extra['channel_id']) + elems = { + 'channel': self.guild.get_channel(channel_id) or Object(id=channel_id), + 'message_id': int(self.extra['message_id']), + } + self.extra: _AuditLogProxyPinAction = type('_AuditLogProxy', (), elems)() + elif self.action.name.startswith('overwrite_'): + # the overwrite_ actions have a dict with some information + instance_id = int(self.extra['id']) + the_type = self.extra.get('type') + if the_type == '1': + self.extra = self._get_member(instance_id) + elif the_type == '0': + role = self.guild.get_role(instance_id) + if role is None: + role = Object(id=instance_id) + role.name = self.extra.get('role_name') # type: ignore + self.extra: Role = role + elif self.action.name.startswith('stage_instance'): + channel_id = int(self.extra['channel_id']) + elems = {'channel': self.guild.get_channel(channel_id) or Object(id=channel_id)} + self.extra: _AuditLogProxyStageInstanceAction = type('_AuditLogProxy', (), elems)() + + # fmt: off + self.extra: Union[ + _AuditLogProxyMemberPrune, + _AuditLogProxyMemberMoveOrMessageDelete, + _AuditLogProxyMemberDisconnect, + _AuditLogProxyPinAction, + _AuditLogProxyStageInstanceAction, + Member, User, None, + Role, + ] + # fmt: on + + # this key is not present when the above is present, typically. + # It's a list of { new_value: a, old_value: b, key: c } + # where new_value and old_value are not guaranteed to be there depending + # on the action type, so let's just fetch it for now and only turn it + # into meaningful data when requested + self._changes = data.get('changes', []) + + self.user = self._get_member(utils._get_as_snowflake(data, 'user_id')) # type: ignore + self._target_id = utils._get_as_snowflake(data, 'target_id') + + def _get_member(self, user_id: int) -> Union[Member, User, None]: + return self.guild.get_member(user_id) or self._users.get(user_id) + + def __repr__(self) -> str: + return f'' + + @utils.cached_property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the entry's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @utils.cached_property + def target(self) -> Union[Guild, abc.GuildChannel, Member, User, Role, Invite, Emoji, StageInstance, GuildSticker, Thread, Object, None]: + try: + converter = getattr(self, '_convert_target_' + self.action.target_type) + except AttributeError: + return Object(id=self._target_id) + else: + return converter(self._target_id) + + @utils.cached_property + def category(self) -> enums.AuditLogActionCategory: + """Optional[:class:`AuditLogActionCategory`]: The category of the action, if applicable.""" + return self.action.category + + @utils.cached_property + def changes(self) -> AuditLogChanges: + """:class:`AuditLogChanges`: The list of changes this entry has.""" + obj = AuditLogChanges(self, self._changes) + del self._changes + return obj + + @utils.cached_property + def before(self) -> AuditLogDiff: + """:class:`AuditLogDiff`: The target's prior state.""" + return self.changes.before + + @utils.cached_property + def after(self) -> AuditLogDiff: + """:class:`AuditLogDiff`: The target's subsequent state.""" + return self.changes.after + + def _convert_target_guild(self, target_id: int) -> Guild: + return self.guild + + def _convert_target_channel(self, target_id: int) -> Union[abc.GuildChannel, Object]: + return self.guild.get_channel(target_id) or Object(id=target_id) + + def _convert_target_user(self, target_id: int) -> Union[Member, User, None]: + return self._get_member(target_id) + + def _convert_target_role(self, target_id: int) -> Union[Role, Object]: + return self.guild.get_role(target_id) or Object(id=target_id) + + def _convert_target_invite(self, target_id: int) -> Invite: + # invites have target_id set to null + # so figure out which change has the full invite data + changeset = self.before if self.action is enums.AuditLogAction.invite_delete else self.after + + fake_payload = { + 'max_age': changeset.max_age, + 'max_uses': changeset.max_uses, + 'code': changeset.code, + 'temporary': changeset.temporary, + 'uses': changeset.uses, + } + + obj = Invite(state=self._state, data=fake_payload, guild=self.guild, channel=changeset.channel) # type: ignore + try: + obj.inviter = changeset.inviter + except AttributeError: + pass + return obj + + def _convert_target_emoji(self, target_id: int) -> Union[Emoji, Object]: + return self._state.get_emoji(target_id) or Object(id=target_id) + + def _convert_target_message(self, target_id: int) -> Union[Member, User, None]: + 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) diff --git a/build/lib/discord/backoff.py b/build/lib/discord/backoff.py new file mode 100644 index 0000000000..c00b424f13 --- /dev/null +++ b/build/lib/discord/backoff.py @@ -0,0 +1,106 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + + +import time +import random +from typing import Callable, Generic, Literal, TypeVar, overload, Union + +T = TypeVar('T', bool, Literal[True], Literal[False]) + +__all__ = ( + 'ExponentialBackoff', +) + +class ExponentialBackoff(Generic[T]): + """An implementation of the exponential backoff algorithm + + Provides a convenient interface to implement an exponential backoff + for reconnecting or retrying transmissions in a distributed network. + + Once instantiated, the delay method will return the next interval to + wait for when retrying a connection or transmission. The maximum + delay increases exponentially with each retry up to a maximum of + 2^10 * base, and is reset if no more attempts are needed in a period + of 2^11 * base seconds. + + Parameters + ---------- + base: :class:`int` + The base delay in seconds. The first retry-delay will be up to + this many seconds. + integral: :class:`bool` + Set to ``True`` if whole periods of base is desirable, otherwise any + number in between may be returned. + """ + + def __init__(self, base: int = 1, *, integral: T = False): + self._base: int = base + + self._exp: int = 0 + self._max: int = 10 + self._reset_time: int = base * 2 ** 11 + self._last_invocation: float = time.monotonic() + + # Use our own random instance to avoid messing with global one + rand = random.Random() + rand.seed() + + self._randfunc: Callable[..., Union[int, float]] = rand.randrange if integral else rand.uniform # type: ignore + + @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 + + Returns the next delay to wait according to the exponential + backoff algorithm. This is a value between 0 and base * 2^exp + where exponent starts off at 1 and is incremented at every + invocation of this method up to a maximum of 10. + + If a period of more than base * 2^11 has passed since the last + retry, the exponent is reset to 1. + """ + invocation = time.monotonic() + interval = invocation - self._last_invocation + self._last_invocation = invocation + + if interval > self._reset_time: + self._exp = 0 + + self._exp = min(self._exp + 1, self._max) + return self._randfunc(0, self._base * 2 ** self._exp) diff --git a/build/lib/discord/bin/libopus-0.x64.dll b/build/lib/discord/bin/libopus-0.x64.dll new file mode 100644 index 0000000000000000000000000000000000000000..74a8e3554ff00d07d979d7763f03555d8e392f21 GIT binary patch literal 441856 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%0DfdGRmjsdzH`o8kKLF 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

?{OTS8Mq@SdJN#9A| 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Ν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 B;I9Ax literal 0 HcmV?d00001 diff --git a/build/lib/discord/bin/libopus-0.x86.dll b/build/lib/discord/bin/libopus-0.x86.dll new file mode 100644 index 0000000000000000000000000000000000000000..ee71317fa6291e8a015b57b5b42a968b49ceb7fd GIT binary patch literal 366080 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

ss8*YJa`<&!F4OE!PF< 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 None: + super().__init__(*args, **kwargs) + self._pending_application_commands = [] + self._application_commands = {} + + @property + def pending_application_commands(self): + return self._pending_application_commands + + @property + def commands(self) -> List[Union[ApplicationCommand, Any]]: + commands = self.application_commands + if self._supports_prefixed_commands: + commands += self.prefixed_commands + return commands + + @property + def application_commands(self) -> List[ApplicationCommand]: + return list(self._application_commands.values()) + + def add_application_command(self, command: ApplicationCommand) -> None: + """Adds a :class:`.ApplicationCommand` into the internal list of commands. + + This is usually not called, instead the :meth:`~.ApplicationMixin.command` or + other shortcut decorators are used instead. + + .. versionadded:: 2.0 + + Parameters + ----------- + command: :class:`.ApplicationCommand` + The command to add. + """ + if isinstance(command, SlashCommand) and command.is_subcommand: + raise TypeError("The provided command is a sub-command of group") + + if self.debug_guilds and command.guild_ids is None: + command.guild_ids = self.debug_guilds + self._pending_application_commands.append(command) + + def remove_application_command( + self, command: ApplicationCommand + ) -> Optional[ApplicationCommand]: + """Remove a :class:`.ApplicationCommand` from the internal list + of commands. + + .. versionadded:: 2.0 + + Parameters + ----------- + command: :class:`.ApplicationCommand` + The command to remove. + + Returns + -------- + Optional[:class:`.ApplicationCommand`] + The command that was removed. If the name is not valid then + ``None`` is returned instead. + """ + return self._application_commands.pop(command.id) + + @property + def get_command(self): + """Shortcut for :meth:`.get_application_command`. + + .. note:: + Overridden in :class:`ext.commands.Bot`. + + .. versionadded:: 2.0 + """ + # TODO: Do something like we did in self.commands for this + return self.get_application_command + + def get_application_command( + self, + name: str, + guild_ids: Optional[List[int]] = None, + type: Type[ApplicationCommand] = SlashCommand, + ) -> Optional[ApplicationCommand]: + """Get a :class:`.ApplicationCommand` from the internal list + of commands. + + .. versionadded:: 2.0 + + Parameters + ----------- + name: :class:`str` + The name of the command to get. + guild_ids: List[:class:`int`] + The guild ids associated to the command to get. + type: Type[:class:`.ApplicationCommand`] + The type of the command to get. Defaults to :class:`.SlashCommand`. + + Returns + -------- + Optional[:class:`.ApplicationCommand`] + The command that was requested. If not found, returns ``None``. + """ + + for command in self._application_commands.values(): + if ( + command.name == name + and isinstance(command, type) + ): + if guild_ids is not None and command.guild_ids != guild_ids: + return + return command + + async def sync_commands(self) -> None: + """|coro| + + Registers all commands that have been added through :meth:`.add_application_command` + since :meth:`.register_commands`. This does not remove any registered commands that are not in the internal + cache, like :meth:`.register_commands` does, but rather just adds new ones. + + This should usually be used instead of :meth:`.register_commands` when commands are already registered and you + want to add more. + + This can cause bugs if you run this command excessively without using register_commands, as the bot's internal + cache can get un-synced with discord's registered commands. + + .. versionadded:: 2.0 + """ + # TODO: Write this function as described in the docstring (bob will do this) + raise NotImplementedError + + async def register_commands(self) -> None: + """|coro| + + Registers all commands that have been added through :meth:`.add_application_command`. + This method cleans up all commands over the API and should sync them with the internal cache of commands. + + By default, this coroutine is called inside the :func:`.on_connect` + event. If you choose to override the :func:`.on_connect` event, then + you should invoke this coroutine as well. + + .. versionadded:: 2.0 + """ + commands = [] + + # Global Command Permissions + global_permissions: List = [] + + registered_commands = await self.http.get_global_commands(self.user.id) + for command in [ + cmd for cmd in self.pending_application_commands if cmd.guild_ids is None + ]: + as_dict = command.to_dict() + if len(registered_commands) > 0: + matches = [ + x + for x in registered_commands + if x["name"] == command.name and x["type"] == command.type + ] + # TODO: rewrite this, it seems inefficient + if matches: + as_dict["id"] = matches[0]["id"] + commands.append(as_dict) + + cmds = await self.http.bulk_upsert_global_commands(self.user.id, commands) + + for i in cmds: + cmd = get( + self.pending_application_commands, + name=i["name"], + guild_ids=None, + type=i["type"], + ) + cmd.id = i["id"] + self._application_commands[cmd.id] = cmd + + # Permissions (Roles will be converted to IDs just before Upsert for Global Commands) + global_permissions.append({"id": i["id"], "permissions": cmd.permissions}) + + update_guild_commands = {} + async for guild in self.fetch_guilds(limit=None): + update_guild_commands[guild.id] = [] + for command in [ + cmd + for cmd in self.pending_application_commands + if cmd.guild_ids is not None + ]: + as_dict = command.to_dict() + for guild_id in command.guild_ids: + to_update = update_guild_commands[guild_id] + update_guild_commands[guild_id] = to_update + [as_dict] + + for guild_id, guild_data in update_guild_commands.items(): + try: + cmds = await self.http.bulk_upsert_guild_commands( + self.user.id, guild_id, update_guild_commands[guild_id] + ) + + # Permissions for this Guild + guild_permissions: List = [] + except Forbidden: + if not guild_data: + continue + print(f"Failed to add command to guild {guild_id}", file=sys.stderr) + raise + else: + for i in cmds: + cmd = find(lambda cmd: cmd.name == i["name"] and cmd.type == i["type"] and int(i["guild_id"]) in cmd.guild_ids, self.pending_application_commands) + cmd.id = i["id"] + self._application_commands[cmd.id] = cmd + + # Permissions + permissions = [ + perm.to_dict() + for perm in cmd.permissions + if perm.guild_id is None + or ( + perm.guild_id == guild_id and perm.guild_id in cmd.guild_ids + ) + ] + guild_permissions.append( + {"id": i["id"], "permissions": permissions} + ) + + for global_command in global_permissions: + permissions = [ + perm.to_dict() + for perm in global_command["permissions"] + if perm.guild_id is None + or ( + perm.guild_id == guild_id and perm.guild_id in cmd.guild_ids + ) + ] + guild_permissions.append( + {"id": global_command["id"], "permissions": permissions} + ) + + # Collect & Upsert Permissions for Each Guild + # Command Permissions for this Guild + guild_cmd_perms: List = [] + + # Loop through Commands Permissions available for this Guild + for item in guild_permissions: + new_cmd_perm = {"id": item["id"], "permissions": []} + + # Replace Role / Owner Names with IDs + for permission in item["permissions"]: + if isinstance(permission["id"], str): + # Replace Role Names + if permission["type"] == 1: + role = get( + self.get_guild(guild_id).roles, + name=permission["id"], + ) + + # If not missing + if role is not None: + new_cmd_perm["permissions"].append( + { + "id": role.id, + "type": 1, + "permission": permission["permission"], + } + ) + else: + print( + "No Role ID found in Guild ({guild_id}) for Role ({role})".format( + guild_id=guild_id, role=permission["id"] + ) + ) + # Add owner IDs + elif ( + permission["type"] == 2 and permission["id"] == "owner" + ): + app = await self.application_info() # type: ignore + if app.team: + for m in app.team.members: + new_cmd_perm["permissions"].append( + { + "id": m.id, + "type": 2, + "permission": permission["permission"], + } + ) + else: + new_cmd_perm["permissions"].append( + { + "id": app.owner.id, + "type": 2, + "permission": permission["permission"], + } + ) + # Add the rest + else: + new_cmd_perm["permissions"].append(permission) + + # Make sure we don't have over 10 overwrites + if len(new_cmd_perm["permissions"]) > 10: + print( + "Command '{name}' has more than 10 permission overrides in guild ({guild_id}).\nwill only use the first 10 permission overrides.".format( + name=self._application_commands[new_cmd_perm["id"]].name, + guild_id=guild_id, + ) + ) + new_cmd_perm["permissions"] = new_cmd_perm["permissions"][:10] + + # Append to guild_cmd_perms + guild_cmd_perms.append(new_cmd_perm) + + # Upsert + try: + await self.http.bulk_upsert_command_permissions( + self.user.id, guild_id, guild_cmd_perms + ) + except Forbidden: + print( + f"Failed to add command permissions to guild {guild_id}", + file=sys.stderr, + ) + raise + + async def process_application_commands(self, interaction: Interaction) -> None: + """|coro| + + This function processes the commands that have been registered + to the bot and other groups. Without this coroutine, none of the + commands will be triggered. + + By default, this coroutine is called inside the :func:`.on_interaction` + event. If you choose to override the :func:`.on_interaction` event, then + you should invoke this coroutine as well. + + This function finds a registered command matching the interaction id from + :attr:`.ApplicationCommandMixin.application_commands` and runs :meth:`ApplicationCommand.invoke` on it. If no matching + command was found, it replies to the interaction with a default message. + + .. versionadded:: 2.0 + + Parameters + ----------- + interaction: :class:`discord.Interaction` + The interaction to process + """ + if interaction.type not in ( + InteractionType.application_command, + InteractionType.auto_complete + ): + return + + try: + command = self._application_commands[interaction.data["id"]] + except KeyError: + self.dispatch("unknown_command", interaction) + else: + if interaction.type is InteractionType.auto_complete: + ctx = await self.get_autocomplete_context(interaction) + ctx.command = command + return await command.invoke_autocomplete_callback(ctx) + + ctx = await self.get_application_context(interaction) + ctx.command = command + self.dispatch("application_command", ctx) + try: + if await self.can_run(ctx, call_once=True): + await ctx.command.invoke(ctx) + else: + raise CheckFailure("The global check once functions failed.") + except DiscordException as exc: + await ctx.command.dispatch_error(ctx, exc) + else: + self.dispatch("application_command_completion", ctx) + + def slash_command(self, **kwargs): + """A shortcut decorator that invokes :func:`.ApplicationCommandMixin.command` and adds it to + the internal command list via :meth:`~.ApplicationCommandMixin.add_application_command`. + This shortcut is made specifically for :class:`.SlashCommand`. + + .. versionadded:: 2.0 + + Returns + -------- + Callable[..., :class:`SlashCommand`] + A decorator that converts the provided method into a :class:`.SlashCommand`, adds it to the bot, + then returns it. + """ + return self.application_command(cls=SlashCommand, **kwargs) + + def user_command(self, **kwargs): + """A shortcut decorator that invokes :func:`.ApplicationCommandMixin.command` and adds it to + the internal command list via :meth:`~.ApplicationCommandMixin.add_application_command`. + This shortcut is made specifically for :class:`.UserCommand`. + + .. versionadded:: 2.0 + + Returns + -------- + Callable[..., :class:`UserCommand`] + A decorator that converts the provided method into a :class:`.UserCommand`, adds it to the bot, + then returns it. + """ + return self.application_command(cls=UserCommand, **kwargs) + + def message_command(self, **kwargs): + """A shortcut decorator that invokes :func:`.ApplicationCommandMixin.command` and adds it to + the internal command list via :meth:`~.ApplicationCommandMixin.add_application_command`. + This shortcut is made specifically for :class:`.MessageCommand`. + + .. versionadded:: 2.0 + + Returns + -------- + Callable[..., :class:`MessageCommand`] + A decorator that converts the provided method into a :class:`.MessageCommand`, adds it to the bot, + then returns it. + """ + return self.application_command(cls=MessageCommand, **kwargs) + + def application_command(self, **kwargs): + """A shortcut decorator that invokes :func:`.command` and adds it to + the internal command list via :meth:`~.ApplicationCommandMixin.add_application_command`. + + .. versionadded:: 2.0 + + Returns + -------- + Callable[..., :class:`ApplicationCommand`] + A decorator that converts the provided method into an :class:`.ApplicationCommand`, adds it to the bot, + then returns it. + """ + + def decorator(func) -> ApplicationCommand: + result = command(**kwargs)(func) + self.add_application_command(result) + return result + + return decorator + + def command(self, **kwargs): + """There is an alias for :meth:`application_command`. + + .. note:: + + This decorator is overridden by :class:`discord.ext.commands.Bot`. + + .. versionadded:: 2.0 + + Returns + -------- + Callable[..., :class:`ApplicationCommand`] + A decorator that converts the provided method into an :class:`.ApplicationCommand`, adds it to the bot, + then returns it. + """ + return self.application_command(**kwargs) + + def create_group( + self, + name: str, + description: Optional[str] = None, + guild_ids: Optional[List[int]] = None, + ) -> SlashCommandGroup: + """A shortcut method that creates a slash command group with no subcommands and adds it to the internal + command list via :meth:`~.ApplicationCommandMixin.add_application_command`. + + .. versionadded:: 2.0 + + Parameters + ---------- + name: :class:`str` + The name of the group to create. + description: Optional[:class:`str`] + The description of the group to create. + guild_ids: Optional[List[:class:`int`]] + A list of the IDs of each guild this group should be added to, making it a guild command. + This will be a global command if ``None`` is passed. + + Returns + -------- + SlashCommandGroup + The slash command group that was created. + """ + description = description or "No description provided." + group = SlashCommandGroup(name, description, guild_ids) + self.add_application_command(group) + return group + + def group( + self, + name: str, + description: Optional[str] = None, + guild_ids: Optional[List[int]] = None, + ) -> Callable[[Type[SlashCommandGroup]], SlashCommandGroup]: + """A shortcut decorator that initializes the provided subclass of :class:`.SlashCommandGroup` + and adds it to the internal command list via :meth:`~.ApplicationCommandMixin.add_application_command`. + + .. versionadded:: 2.0 + + Parameters + ---------- + name: :class:`str` + The name of the group to create. + description: Optional[:class:`str`] + The description of the group to create. + guild_ids: Optional[List[:class:`int`]] + A list of the IDs of each guild this group should be added to, making it a guild command. + This will be a global command if ``None`` is passed. + + Returns + -------- + Callable[[Type[SlashCommandGroup]], SlashCommandGroup] + The slash command group that was created. + """ + def inner(cls: Type[SlashCommandGroup]) -> SlashCommandGroup: + group = cls( + name, + ( + description or inspect.cleandoc(cls.__doc__).splitlines()[0] + if cls.__doc__ is not None else "No description provided" + ), + guild_ids=guild_ids + ) + self.add_application_command(group) + return group + return inner + + slash_group = group + + async def get_application_context( + self, interaction: Interaction, cls=None + ) -> ApplicationContext: + r"""|coro| + + Returns the invocation context from the interaction. + + This is a more low-level counter-part for :meth:`.process_application_commands` + to allow users more fine grained control over the processing. + + Parameters + ----------- + interaction: :class:`discord.Interaction` + The interaction to get the invocation context from. + cls + The factory class that will be used to create the context. + By default, this is :class:`.ApplicationContext`. Should a custom + class be provided, it must be similar enough to + :class:`.ApplicationContext`\'s interface. + + Returns + -------- + :class:`.ApplicationContext` + The invocation context. The type of this can change via the + ``cls`` parameter. + """ + if cls is None: + cls = ApplicationContext + return cls(self, interaction) + + async def get_autocomplete_context( + self, interaction: Interaction, cls=None + ) -> AutocompleteContext: + r"""|coro| + + Returns the autocomplete context from the interaction. + + This is a more low-level counter-part for :meth:`.process_application_commands` + to allow users more fine grained control over the processing. + + Parameters + ----------- + interaction: :class:`discord.Interaction` + The interaction to get the invocation context from. + cls + The factory class that will be used to create the context. + By default, this is :class:`.AutocompleteContext`. Should a custom + class be provided, it must be similar enough to + :class:`.AutocompleteContext`\'s interface. + + Returns + -------- + :class:`.AutocompleteContext` + The autocomplete context. The type of this can change via the + ``cls`` parameter. + """ + if cls is None: + cls = AutocompleteContext + return cls(self, interaction) + + + +class BotBase(ApplicationCommandMixin, CogMixin): + _supports_prefixed_commands = False + # TODO I think + def __init__(self, description=None, *args, **options): + # super(Client, self).__init__(*args, **kwargs) + # I replaced ^ with v and it worked + super().__init__(*args, **options) + self.extra_events = {} # TYPE: Dict[str, List[CoroFunc]] + self.__cogs = {} # TYPE: Dict[str, Cog] + self.__extensions = {} # TYPE: Dict[str, types.ModuleType] + self._checks = [] # TYPE: List[Check] + self._check_once = [] + self._before_invoke = None + self._after_invoke = None + self.description = inspect.cleandoc(description) if description else "" + self.owner_id = options.get("owner_id") + self.owner_ids = options.get("owner_ids", set()) + + self.debug_guild = options.pop( + "debug_guild", None + ) # TODO: remove or reimplement + self.debug_guilds = options.pop("debug_guilds", None) + + if self.owner_id and self.owner_ids: + raise TypeError("Both owner_id and owner_ids are set.") + + if self.owner_ids and not isinstance( + self.owner_ids, collections.abc.Collection + ): + raise TypeError( + f"owner_ids must be a collection not {self.owner_ids.__class__!r}" + ) + + if self.debug_guild: + if self.debug_guilds is None: + self.debug_guilds = [self.debug_guild] + else: + raise TypeError("Both debug_guild and debug_guilds are set.") + + self._checks = [] + self._check_once = [] + self._before_invoke = None + self._after_invoke = None + + async def on_connect(self): + await self.register_commands() + + async def on_interaction(self, interaction): + await self.process_application_commands(interaction) + + async def on_application_command_error( + self, context: ApplicationContext, exception: DiscordException + ) -> None: + """|coro| + + The default command error handler provided by the bot. + + By default this prints to :data:`sys.stderr` however it could be + overridden to have a different implementation. + + This only fires if you do not specify any listeners for command error. + """ + if self.extra_events.get('on_application_command_error', None): + return + + command = context.command + if command and command.has_error_handler(): + return + + cog = context.cog + if cog and cog.has_error_handler(): + return + + print(f"Ignoring exception in command {context.command}:", file=sys.stderr) + traceback.print_exception( + type(exception), exception, exception.__traceback__, file=sys.stderr + ) + + # global check registration + # TODO: Remove these from commands.Bot + + def check(self, func): + r"""A decorator that adds a global check to the bot. + A global check is similar to a :func:`.check` that is applied + on a per command basis except it is run before any command checks + have been verified and applies to every command the bot has. + + .. note:: + + This function can either be a regular function or a coroutine. + Similar to a command :func:`.check`\, this takes a single parameter + of type :class:`.Context` and can only raise exceptions inherited from + :exc:`.CommandError`. + + Example + --------- + .. code-block:: python3 + + @bot.check + def check_commands(ctx): + return ctx.command.qualified_name in allowed_commands + + """ + # T was used instead of Check to ensure the type matches on return + self.add_check(func) # type: ignore + return func + + def add_check(self, func, *, call_once: bool = False) -> None: + """Adds a global check to the bot. + This is the non-decorator interface to :meth:`.check` + and :meth:`.check_once`. + + Parameters + ----------- + func + The function that was used as a global check. + call_once: :class:`bool` + If the function should only be called once per + :meth:`.invoke` call. + + """ + + if call_once: + self._check_once.append(func) + else: + self._checks.append(func) + + def remove_check(self, func, *, call_once: bool = False) -> None: + """Removes a global check from the bot. + This function is idempotent and will not raise an exception + if the function is not in the global checks. + + Parameters + ----------- + func + The function to remove from the global checks. + call_once: :class:`bool` + If the function was added with ``call_once=True`` in + the :meth:`.Bot.add_check` call or using :meth:`.check_once`. + + """ + l = self._check_once if call_once else self._checks + + try: + l.remove(func) + except ValueError: + pass + + def check_once(self, func): + r"""A decorator that adds a "call once" global check to the bot. + Unlike regular global checks, this one is called only once + per :meth:`.invoke` call. + Regular global checks are called whenever a command is called + or :meth:`.Command.can_run` is called. This type of check + bypasses that and ensures that it's called only once, even inside + the default help command. + + .. note:: + + When using this function the :class:`.Context` sent to a group subcommand + may only parse the parent command and not the subcommands due to it + being invoked once per :meth:`.Bot.invoke` call. + + .. note:: + + This function can either be a regular function or a coroutine. + Similar to a command :func:`.check`\, this takes a single parameter + of type :class:`.Context` and can only raise exceptions inherited from + :exc:`.CommandError`. + + Example + --------- + .. code-block:: python3 + + @bot.check_once + def whitelist(ctx): + return ctx.message.author.id in my_whitelist + + """ + self.add_check(func, call_once=True) + return func + + async def can_run( + self, ctx: ApplicationContext, *, call_once: bool = False + ) -> bool: + data = self._check_once if call_once else self._checks + + if len(data) == 0: + return True + + # type-checker doesn't distinguish between functions and methods + return await async_all(f(ctx) for f in data) # type: ignore + + # listener registration + + def add_listener(self, func: CoroFunc, name: str = MISSING) -> None: + """The non decorator alternative to :meth:`.listen`. + + Parameters + ----------- + func: :ref:`coroutine ` + The function to call. + name: :class:`str` + The name of the event to listen for. Defaults to ``func.__name__``. + + Example + -------- + + .. code-block:: python3 + + async def on_ready(): pass + async def my_message(message): pass + + bot.add_listener(on_ready) + bot.add_listener(my_message, 'on_message') + """ + name = func.__name__ if name is MISSING else name + + if not asyncio.iscoroutinefunction(func): + raise TypeError('Listeners must be coroutines') + + if name in self.extra_events: + self.extra_events[name].append(func) + else: + self.extra_events[name] = [func] + + def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None: + """Removes a listener from the pool of listeners. + + Parameters + ----------- + func + The function that was used as a listener to remove. + name: :class:`str` + The name of the event we want to remove. Defaults to + ``func.__name__``. + """ + + name = func.__name__ if name is MISSING else name + + if name in self.extra_events: + try: + self.extra_events[name].remove(func) + except ValueError: + pass + + def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]: + """A decorator that registers another function as an external + event listener. Basically this allows you to listen to multiple + events from different places e.g. such as :func:`.on_ready` + + The functions being listened to must be a :ref:`coroutine `. + + Example + -------- + + .. code-block:: python3 + + @bot.listen() + async def on_message(message): + print('one') + + # in some other file... + + @bot.listen('on_message') + async def my_message(message): + print('two') + + Would print one and two in an unspecified order. + + Raises + ------- + TypeError + The function being listened to is not a coroutine. + """ + + def decorator(func: CFT) -> CFT: + self.add_listener(func, name) + return func + + return decorator + + def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None: + # super() will resolve to Client + super().dispatch(event_name, *args, **kwargs) # type: ignore + ev = 'on_' + event_name + for event in self.extra_events.get(ev, []): + self._schedule_event(event, ev, *args, **kwargs) # type: ignore + + def before_invoke(self, coro): + """A decorator that registers a coroutine as a pre-invoke hook. + A pre-invoke hook is called directly before the command is + called. This makes it a useful function to set up database + connections or any type of set up required. + This pre-invoke hook takes a sole parameter, a :class:`.Context`. + + .. note:: + + The :meth:`~.Bot.before_invoke` and :meth:`~.Bot.after_invoke` hooks are + only called if all checks and argument parsing procedures pass + without error. If any check or argument parsing procedures fail + then the hooks are not called. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the pre-invoke hook. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError("The pre-invoke hook must be a coroutine.") + + self._before_invoke = coro + return coro + + def after_invoke(self, coro): + r"""A decorator that registers a coroutine as a post-invoke hook. + A post-invoke hook is called directly after the command is + called. This makes it a useful function to clean-up database + connections or any type of clean up required. + This post-invoke hook takes a sole parameter, a :class:`.Context`. + + .. note:: + + Similar to :meth:`~.Bot.before_invoke`\, this is not called unless + checks and argument parsing procedures succeed. This hook is, + however, **always** called regardless of the internal command + callback raising an error (i.e. :exc:`.CommandInvokeError`\). + This makes it ideal for clean-up scenarios. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the post-invoke hook. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError("The post-invoke hook must be a coroutine.") + + self._after_invoke = coro + return coro + + +class Bot(BotBase, Client): + """Represents a discord bot. + + This class is a subclass of :class:`discord.Client` and as a result + anything that you can do with a :class:`discord.Client` you can do with + this bot. + + This class also subclasses :class:`.ApplicationCommandMixin` to provide the functionality + to manage commands. + + .. versionadded:: 2.0 + + Attributes + ----------- + description: :class:`str` + The content prefixed into the default help message. + owner_id: Optional[:class:`int`] + The user ID that owns the bot. If this is not set and is then queried via + :meth:`.is_owner` then it is fetched automatically using + :meth:`~.Bot.application_info`. + owner_ids: Optional[Collection[:class:`int`]] + The user IDs that owns the bot. This is similar to :attr:`owner_id`. + If this is not set and the application is team based, then it is + fetched automatically using :meth:`~.Bot.application_info`. + For performance reasons it is recommended to use a :class:`set` + for the collection. You cannot set both ``owner_id`` and ``owner_ids``. + + .. versionadded:: 1.3 + debug_guild: Optional[:class:`int`] + Guild ID of a guild to use for testing commands. Prevents setting global commands + in favor of guild commands, which update instantly. + .. note:: + + The bot will not create any global commands if a debug_guild is passed. + debug_guilds: Optional[List[:class:`int`]] + Guild IDs of guilds to use for testing commands. This is similar to debug_guild. + .. note:: + + You cannot set both debug_guild and debug_guilds. + """ + + pass + + +class AutoShardedBot(BotBase, AutoShardedClient): + """This is similar to :class:`.Bot` except that it is inherited from + :class:`discord.AutoShardedClient` instead. + + .. versionadded:: 2.0 + """ + + pass diff --git a/build/lib/discord/channel.py b/build/lib/discord/channel.py new file mode 100644 index 0000000000..17c9048236 --- /dev/null +++ b/build/lib/discord/channel.py @@ -0,0 +1,2136 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import time +import asyncio +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Mapping, + Optional, + TYPE_CHECKING, + Tuple, + Type, + TypeVar, + Union, + overload, +) +import datetime + +import discord.abc +from .permissions import PermissionOverwrite, Permissions +from .enums import ChannelType, EmbeddedActivity, InviteTarget, StagePrivacyLevel, try_enum, VoiceRegion, VideoQualityMode +from .mixins import Hashable +from .object import Object +from . import utils +from .utils import MISSING +from .asset import Asset +from .errors import ClientException, InvalidArgument +from .stage_instance import StageInstance +from .threads import Thread +from .iterators import ArchivedThreadIterator +from .invite import Invite + +__all__ = ( + 'TextChannel', + 'VoiceChannel', + 'StageChannel', + 'DMChannel', + 'CategoryChannel', + 'StoreChannel', + 'GroupChannel', + 'PartialMessageable', +) + +if TYPE_CHECKING: + from .types.threads import ThreadArchiveDuration + from .role import Role + from .member import Member, VoiceState + from .abc import Snowflake, SnowflakeTime + from .message import Message, PartialMessage + from .webhook import Webhook + from .state import ConnectionState + from .user import ClientUser, User, BaseUser + from .guild import Guild, GuildChannel as GuildChannelType + from .types.channel import ( + TextChannel as TextChannelPayload, + VoiceChannel as VoiceChannelPayload, + StageChannel as StageChannelPayload, + DMChannel as DMChannelPayload, + CategoryChannel as CategoryChannelPayload, + StoreChannel as StoreChannelPayload, + GroupDMChannel as GroupChannelPayload, + ) + from .types.snowflake import SnowflakeList + + +async def _single_delete_strategy(messages: Iterable[Message]): + for m in messages: + await m.delete() + + +class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): + """Represents a Discord guild text channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns the channel's name. + + Attributes + ----------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + topic: Optional[:class:`str`] + The channel's topic. ``None`` if it doesn't exist. + position: :class:`int` + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. + last_message_id: Optional[:class:`int`] + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages + in this channel. A value of `0` denotes that it is disabled. + Bots and users with :attr:`~Permissions.manage_channels` or + :attr:`~Permissions.manage_messages` bypass slowmode. + nsfw: :class:`bool` + If the channel is marked as "not safe for work". + + .. note:: + + To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for threads created in this channel. + + .. versionadded:: 2.0 + """ + + __slots__ = ( + 'name', + 'id', + 'guild', + 'topic', + '_state', + 'nsfw', + 'category_id', + 'position', + 'slowmode_delay', + '_overwrites', + '_type', + 'last_message_id', + 'default_auto_archive_duration', + ) + + def __init__(self, *, state: ConnectionState, guild: Guild, data: TextChannelPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self._type: int = data['type'] + self._update(guild, data) + + def __repr__(self) -> str: + attrs = [ + ('id', self.id), + ('name', self.name), + ('position', self.position), + ('nsfw', self.nsfw), + ('news', self.is_news()), + ('category_id', self.category_id), + ] + joined = ' '.join('%s=%r' % t for t in attrs) + return f'<{self.__class__.__name__} {joined}>' + + def _update(self, guild: Guild, data: TextChannelPayload) -> None: + self.guild: Guild = guild + self.name: str = data['name'] + self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id') + self.topic: Optional[str] = data.get('topic') + self.position: int = data['position'] + self.nsfw: bool = data.get('nsfw', False) + # Does this need coercion into `int`? No idea yet. + self.slowmode_delay: int = data.get('rate_limit_per_user', 0) + self.default_auto_archive_duration: ThreadArchiveDuration = data.get('default_auto_archive_duration', 1440) + self._type: int = data.get('type', self._type) + self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id') + self._fill_overwrites(data) + + async def _get_channel(self): + return self + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return try_enum(ChannelType, self._type) + + @property + def _sorting_bucket(self) -> int: + return ChannelType.text.value + + @utils.copy_doc(discord.abc.GuildChannel.permissions_for) + def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + base = super().permissions_for(obj) + + # text channels do not have voice related permissions + denied = Permissions.voice() + base.value &= ~denied.value + return base + + @property + def members(self) -> List[Member]: + """List[:class:`Member`]: Returns all members that can see this channel.""" + return [m for m in self.guild.members if self.permissions_for(m).read_messages] + + @property + def threads(self) -> List[Thread]: + """List[:class:`Thread`]: Returns all the threads that you can see. + + .. versionadded:: 2.0 + """ + return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id] + + def is_nsfw(self) -> bool: + """:class:`bool`: Checks if the channel is NSFW.""" + return self.nsfw + + def is_news(self) -> bool: + """:class:`bool`: Checks if the channel is a news channel.""" + return self._type == ChannelType.news.value + + @property + def last_message(self) -> Optional[Message]: + """Fetches the last message from this channel in cache. + + The message might not be valid or point to an existing message. + + .. admonition:: Reliable Fetching + :class: helpful + + For a slightly more reliable method of fetching the + last message, consider using either :meth:`history` + or :meth:`fetch_message` with the :attr:`last_message_id` + attribute. + + Returns + --------- + Optional[:class:`Message`] + The last message in this channel or ``None`` if not found. + """ + return self._state._get_message(self.last_message_id) if self.last_message_id else None + + @overload + async def edit( + self, + *, + reason: Optional[str] = ..., + name: str = ..., + topic: str = ..., + position: int = ..., + nsfw: bool = ..., + sync_permissions: bool = ..., + category: Optional[CategoryChannel] = ..., + slowmode_delay: int = ..., + default_auto_archive_duration: ThreadArchiveDuration = ..., + type: ChannelType = ..., + overwrites: Mapping[Union[Role, Member, Snowflake], PermissionOverwrite] = ..., + ) -> Optional[TextChannel]: + ... + + @overload + async def edit(self) -> Optional[TextChannel]: + ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 1.3 + The ``overwrites`` keyword-only parameter was added. + + .. versionchanged:: 1.4 + The ``type`` keyword-only parameter was added. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + Parameters + ---------- + name: :class:`str` + The new channel name. + topic: :class:`str` + The new channel's topic. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + To mark the channel as NSFW or not. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing + category. Defaults to ``False``. + category: Optional[:class:`CategoryChannel`] + The new category for this channel. Can be ``None`` to remove the + category. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for user in this channel, in seconds. + A value of `0` disables slowmode. The maximum value possible is `21600`. + type: :class:`ChannelType` + Change the type of this text channel. Currently, only conversion between + :attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This + is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`. + reason: Optional[:class:`str`] + The reason for editing this channel. Shows up on the audit log. + overwrites: :class:`Mapping` + A :class:`Mapping` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply to the channel. + default_auto_archive_duration: :class:`int` + The new default auto archive duration in minutes for threads created in this channel. + Must be one of ``60``, ``1440``, ``4320``, or ``10080``. + + Raises + ------ + InvalidArgument + If position is less than 0 or greater than the number of channels, or if + the permission overwrite information is not in proper form. + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + + Returns + -------- + Optional[:class:`.TextChannel`] + The newly edited text channel. If the edit was only positional + then ``None`` is returned instead. + """ + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + + @utils.copy_doc(discord.abc.GuildChannel.clone) + async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> TextChannel: + return await self._clone_impl( + {'topic': self.topic, 'nsfw': self.nsfw, 'rate_limit_per_user': self.slowmode_delay}, name=name, reason=reason + ) + + async def delete_messages(self, messages: Iterable[Snowflake]) -> None: + """|coro| + + Deletes a list of messages. This is similar to :meth:`Message.delete` + except it bulk deletes multiple messages. + + As a special case, if the number of messages is 0, then nothing + is done. If the number of messages is 1 then single message + delete is done. If it's more than two, then bulk delete is used. + + You cannot bulk delete more than 100 messages or messages that + are older than 14 days old. + + You must have the :attr:`~Permissions.manage_messages` permission to + use this. + + Parameters + ----------- + messages: Iterable[:class:`abc.Snowflake`] + An iterable of messages denoting which ones to bulk delete. + + Raises + ------ + ClientException + The number of messages to delete was more than 100. + Forbidden + You do not have proper permissions to delete the messages. + NotFound + If single delete, then the message was already deleted. + HTTPException + Deleting the messages failed. + """ + if not isinstance(messages, (list, tuple)): + messages = list(messages) + + if len(messages) == 0: + return # do nothing + + if len(messages) == 1: + message_id: int = messages[0].id + await self._state.http.delete_message(self.id, message_id) + return + + if len(messages) > 100: + raise ClientException('Can only bulk delete messages up to 100 messages') + + message_ids: SnowflakeList = [m.id for m in messages] + await self._state.http.delete_messages(self.id, message_ids) + + async def purge( + self, + *, + limit: Optional[int] = 100, + check: Callable[[Message], bool] = MISSING, + before: Optional[SnowflakeTime] = None, + after: Optional[SnowflakeTime] = None, + around: Optional[SnowflakeTime] = None, + oldest_first: Optional[bool] = False, + bulk: bool = True, + ) -> List[Message]: + """|coro| + + Purges a list of messages that meet the criteria given by the predicate + ``check``. If a ``check`` is not provided then all messages are deleted + without discrimination. + + You must have the :attr:`~Permissions.manage_messages` permission to + delete messages even if they are your own. + The :attr:`~Permissions.read_message_history` permission is + also needed to retrieve message history. + + Examples + --------- + + Deleting bot's messages :: + + def is_me(m): + return m.author == client.user + + deleted = await channel.purge(limit=100, check=is_me) + await channel.send(f'Deleted {len(deleted)} message(s)') + + Parameters + ----------- + limit: Optional[:class:`int`] + The number of messages to search through. This is not the number + of messages that will be deleted, though it can be. + check: Callable[[:class:`Message`], :class:`bool`] + The function used to check if a message should be deleted. + It must take a :class:`Message` as its sole parameter. + before: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Same as ``before`` in :meth:`history`. + after: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Same as ``after`` in :meth:`history`. + around: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Same as ``around`` in :meth:`history`. + oldest_first: Optional[:class:`bool`] + Same as ``oldest_first`` in :meth:`history`. + bulk: :class:`bool` + If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting + a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will + fall back to single delete if messages are older than two weeks. + + Raises + ------- + Forbidden + You do not have proper permissions to do the actions required. + HTTPException + Purging the messages failed. + + Returns + -------- + List[:class:`.Message`] + The list of messages that were deleted. + """ + + if check is MISSING: + check = lambda m: True + + iterator = self.history(limit=limit, before=before, after=after, oldest_first=oldest_first, around=around) + ret: List[Message] = [] + count = 0 + + minimum_time = int((time.time() - 14 * 24 * 60 * 60) * 1000.0 - 1420070400000) << 22 + strategy = self.delete_messages if bulk else _single_delete_strategy + + async for message in iterator: + if count == 100: + to_delete = ret[-100:] + await strategy(to_delete) + count = 0 + await asyncio.sleep(1) + + if not check(message): + continue + + if message.id < minimum_time: + # older than 14 days old + if count == 1: + await ret[-1].delete() + elif count >= 2: + to_delete = ret[-count:] + await strategy(to_delete) + + count = 0 + strategy = _single_delete_strategy + + count += 1 + ret.append(message) + + # SOme messages remaining to poll + if count >= 2: + # more than 2 messages -> bulk delete + to_delete = ret[-count:] + await strategy(to_delete) + elif count == 1: + # delete a single message + await ret[-1].delete() + + return ret + + async def webhooks(self) -> List[Webhook]: + """|coro| + + Gets the list of webhooks from this channel. + + Requires :attr:`~.Permissions.manage_webhooks` permissions. + + Raises + ------- + Forbidden + You don't have permissions to get the webhooks. + + Returns + -------- + List[:class:`Webhook`] + The webhooks for this channel. + """ + + from .webhook import Webhook + + data = await self._state.http.channel_webhooks(self.id) + return [Webhook.from_state(d, state=self._state) for d in data] + + async def create_webhook(self, *, name: str, avatar: Optional[bytes] = None, reason: Optional[str] = None) -> Webhook: + """|coro| + + Creates a webhook for this channel. + + Requires :attr:`~.Permissions.manage_webhooks` permissions. + + .. versionchanged:: 1.1 + Added the ``reason`` keyword-only parameter. + + Parameters + ------------- + name: :class:`str` + The webhook's name. + avatar: Optional[:class:`bytes`] + A :term:`py:bytes-like object` representing the webhook's default avatar. + This operates similarly to :meth:`~ClientUser.edit`. + reason: Optional[:class:`str`] + The reason for creating this webhook. Shows up in the audit logs. + + Raises + ------- + HTTPException + Creating the webhook failed. + Forbidden + You do not have permissions to create a webhook. + + Returns + -------- + :class:`Webhook` + The created webhook. + """ + + from .webhook import Webhook + + if avatar is not None: + avatar = utils._bytes_to_base64_data(avatar) # type: ignore + + data = await self._state.http.create_webhook(self.id, name=str(name), avatar=avatar, reason=reason) + return Webhook.from_state(data, state=self._state) + + async def follow(self, *, destination: TextChannel, reason: Optional[str] = None) -> Webhook: + """ + Follows a channel using a webhook. + + Only news channels can be followed. + + .. note:: + + The webhook returned will not provide a token to do webhook + actions, as Discord does not provide it. + + .. versionadded:: 1.3 + + Parameters + ----------- + destination: :class:`TextChannel` + The channel you would like to follow from. + reason: Optional[:class:`str`] + The reason for following the channel. Shows up on the destination guild's audit log. + + .. versionadded:: 1.4 + + Raises + ------- + HTTPException + Following the channel failed. + Forbidden + You do not have the permissions to create a webhook. + + Returns + -------- + :class:`Webhook` + The created webhook. + """ + + if not self.is_news(): + raise ClientException('The channel must be a news channel.') + + if not isinstance(destination, TextChannel): + raise InvalidArgument(f'Expected TextChannel received {destination.__class__.__name__}') + + from .webhook import Webhook + + data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason) + return Webhook._as_follower(data, channel=destination, user=self._state.user) + + 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:: 1.6 + + 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 get_thread(self, thread_id: int, /) -> Optional[Thread]: + """Returns a thread with the given ID. + + .. versionadded:: 2.0 + + Parameters + ----------- + thread_id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`Thread`] + The returned thread or ``None`` if not found. + """ + return self.guild.get_thread(thread_id) + + async def create_thread( + self, + *, + name: str, + message: Optional[Snowflake] = None, + auto_archive_duration: ThreadArchiveDuration = MISSING, + type: Optional[ChannelType] = None, + reason: Optional[str] = None, + ) -> Thread: + """|coro| + + Creates a thread in this text channel. + + To create a public thread, you must have :attr:`~discord.Permissions.create_public_threads`. + For a private thread, :attr:`~discord.Permissions.create_private_threads` is needed instead. + + .. versionadded:: 2.0 + + Parameters + ----------- + name: :class:`str` + The name of the thread. + message: Optional[:class:`abc.Snowflake`] + A snowflake representing the message to create the thread with. + If ``None`` is passed then a private thread is created. + Defaults to ``None``. + auto_archive_duration: :class:`int` + The duration in minutes before a thread is automatically archived for inactivity. + If not provided, the channel's default auto archive duration is used. + type: Optional[:class:`ChannelType`] + The type of thread to create. If a ``message`` is passed then this parameter + is ignored, as a thread created with a message is always a public thread. + By default this creates a private thread if this is ``None``. + reason: :class:`str` + The reason for creating a new thread. Shows up on the audit log. + + Raises + ------- + Forbidden + You do not have permissions to create a thread. + HTTPException + Starting the thread failed. + + Returns + -------- + :class:`Thread` + The created thread + """ + + if type is None: + type = ChannelType.private_thread + + if message is None: + data = await self._state.http.start_thread_without_message( + self.id, + name=name, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + type=type.value, + reason=reason, + ) + else: + data = await self._state.http.start_thread_with_message( + self.id, + message.id, + name=name, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + reason=reason, + ) + + return Thread(guild=self.guild, state=self._state, data=data) + + def archived_threads( + self, + *, + private: bool = False, + joined: bool = False, + limit: Optional[int] = 50, + before: Optional[Union[Snowflake, datetime.datetime]] = None, + ) -> ArchivedThreadIterator: + """Returns an :class:`~discord.AsyncIterator` that iterates over all archived threads in the guild. + + You must have :attr:`~Permissions.read_message_history` to use this. If iterating over private threads + then :attr:`~Permissions.manage_threads` is also required. + + .. versionadded:: 2.0 + + Parameters + ----------- + limit: Optional[:class:`bool`] + The number of threads to retrieve. + If ``None``, retrieves every archived thread in the channel. Note, however, + that this would make it a slow operation. + before: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve archived channels before the given date or ID. + private: :class:`bool` + Whether to retrieve private archived threads. + joined: :class:`bool` + Whether to retrieve private archived threads that you've joined. + You cannot set ``joined`` to ``True`` and ``private`` to ``False``. + + Raises + ------ + Forbidden + You do not have permissions to get archived threads. + HTTPException + The request to get the archived threads failed. + + Yields + ------- + :class:`Thread` + The archived threads. + """ + return ArchivedThreadIterator(self.id, self.guild, limit=limit, joined=joined, private=private, before=before) + + +class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): + __slots__ = ( + 'name', + 'id', + 'guild', + 'bitrate', + 'user_limit', + '_state', + 'position', + '_overwrites', + 'category_id', + 'rtc_region', + 'video_quality_mode', + ) + + def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[VoiceChannelPayload, StageChannelPayload]): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self._update(guild, data) + + def _get_voice_client_key(self) -> Tuple[int, str]: + return self.guild.id, 'guild_id' + + def _get_voice_state_pair(self) -> Tuple[int, int]: + return self.guild.id, self.id + + def _update(self, guild: Guild, data: Union[VoiceChannelPayload, StageChannelPayload]) -> None: + self.guild = guild + self.name: str = data['name'] + rtc = data.get('rtc_region') + self.rtc_region: Optional[VoiceRegion] = try_enum(VoiceRegion, rtc) if rtc is not None else None + self.video_quality_mode: VideoQualityMode = try_enum(VideoQualityMode, data.get('video_quality_mode', 1)) + self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id') + self.position: int = data['position'] + self.bitrate: int = data.get('bitrate') + self.user_limit: int = data.get('user_limit') + self._fill_overwrites(data) + + @property + def _sorting_bucket(self) -> int: + return ChannelType.voice.value + + @property + def members(self) -> List[Member]: + """List[:class:`Member`]: Returns all members that are currently inside this voice channel.""" + ret = [] + for user_id, state in self.guild._voice_states.items(): + if state.channel and state.channel.id == self.id: + member = self.guild.get_member(user_id) + if member is not None: + ret.append(member) + return ret + + @property + def voice_states(self) -> Dict[int, VoiceState]: + """Returns a mapping of member IDs who have voice states in this channel. + + .. versionadded:: 1.3 + + .. note:: + + This function is intentionally low level to replace :attr:`members` + when the member cache is unavailable. + + Returns + -------- + Mapping[:class:`int`, :class:`VoiceState`] + The mapping of member ID to a voice state. + """ + # fmt: off + return { + key: value + for key, value in self.guild._voice_states.items() + if value.channel and value.channel.id == self.id + } + # fmt: on + + @utils.copy_doc(discord.abc.GuildChannel.permissions_for) + def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + base = super().permissions_for(obj) + + # voice channels cannot be edited by people who can't connect to them + # It also implicitly denies all other voice perms + if not base.connect: + denied = Permissions.voice() + denied.update(manage_channels=True, manage_roles=True) + base.value &= ~denied.value + return base + + +class VoiceChannel(VocalGuildChannel): + """Represents a Discord guild voice channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns the channel's name. + + Attributes + ----------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + position: :class:`int` + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. + bitrate: :class:`int` + The channel's preferred audio bitrate in bits per second. + user_limit: :class:`int` + The channel's limit for number of members that can be in a voice channel. + rtc_region: Optional[:class:`VoiceRegion`] + The region for the voice channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + + .. versionadded:: 1.7 + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the voice channel's participants. + + .. versionadded:: 2.0 + """ + + __slots__ = () + + def __repr__(self) -> str: + attrs = [ + ('id', self.id), + ('name', self.name), + ('rtc_region', self.rtc_region), + ('position', self.position), + ('bitrate', self.bitrate), + ('video_quality_mode', self.video_quality_mode), + ('user_limit', self.user_limit), + ('category_id', self.category_id), + ] + joined = ' '.join('%s=%r' % t for t in attrs) + return f'<{self.__class__.__name__} {joined}>' + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.voice + + @utils.copy_doc(discord.abc.GuildChannel.clone) + async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> VoiceChannel: + return await self._clone_impl({'bitrate': self.bitrate, 'user_limit': self.user_limit}, name=name, reason=reason) + + @overload + async def edit( + self, + *, + name: str = ..., + bitrate: int = ..., + user_limit: int = ..., + position: int = ..., + sync_permissions: int = ..., + category: Optional[CategoryChannel] = ..., + overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + rtc_region: Optional[VoiceRegion] = ..., + video_quality_mode: VideoQualityMode = ..., + reason: Optional[str] = ..., + ) -> Optional[VoiceChannel]: + ... + + @overload + async def edit(self) -> Optional[VoiceChannel]: + ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 1.3 + The ``overwrites`` keyword-only parameter was added. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + Parameters + ---------- + name: :class:`str` + The new channel's name. + bitrate: :class:`int` + The new channel's bitrate. + user_limit: :class:`int` + The new channel's user limit. + position: :class:`int` + The new channel's position. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing + category. Defaults to ``False``. + category: Optional[:class:`CategoryChannel`] + The new category for this channel. Can be ``None`` to remove the + category. + reason: Optional[:class:`str`] + The reason for editing this channel. Shows up on the audit log. + overwrites: :class:`Mapping` + A :class:`Mapping` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply to the channel. + rtc_region: Optional[:class:`VoiceRegion`] + The new region for the voice channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + + .. versionadded:: 1.7 + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the voice channel's participants. + + .. versionadded:: 2.0 + + Raises + ------ + InvalidArgument + If the permission overwrite information is not in proper form. + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + + Returns + -------- + Optional[:class:`.VoiceChannel`] + The newly edited voice channel. If the edit was only positional + then ``None`` is returned instead. + """ + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + + async def create_activity_invite(self, activity: Union[EmbeddedActivity, int] , **kwargs) -> Invite: + """|coro| + + A shortcut method that creates an instant activity invite. + + You must have the :attr:`~discord.Permissions.start_embedded_activities` permission to + do this. + + Parameters + ------------ + activity: Union[:class:`discord.EmbeddedActivity`, :class:`int`] + The activity to create an invite for which can be an application id as well. + max_age: :class:`int` + How long the invite should last in seconds. If it's 0 then the invite + doesn't expire. Defaults to ``0``. + max_uses: :class:`int` + How many uses the invite could be used for. If it's 0 then there + are unlimited uses. Defaults to ``0``. + temporary: :class:`bool` + Denotes that the invite grants temporary membership + (i.e. they get kicked after they disconnect). Defaults to ``False``. + unique: :class:`bool` + Indicates if a unique invite URL should be created. Defaults to True. + If this is set to ``False`` then it will return a previously created + invite. + reason: Optional[:class:`str`] + The reason for creating this invite. Shows up on the audit log. + + + Raises + ------- + TypeError + If the activity is not a valid activity or application id. + ~discord.HTTPException + Invite creation failed. + + Returns + -------- + :class:`~discord.Invite` + The invite that was created. + """ + + if isinstance(activity, EmbeddedActivity): + activity = activity.value + + elif not isinstance(activity, int): + raise TypeError('Invalid type provided for the activity.') + + return await self.create_invite( + target_type=InviteTarget.embedded_application, + target_application_id=activity, + **kwargs + ) + +class StageChannel(VocalGuildChannel): + """Represents a Discord guild stage channel. + + .. versionadded:: 1.7 + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns the channel's name. + + Attributes + ----------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + topic: Optional[:class:`str`] + The channel's topic. ``None`` if it isn't set. + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + position: :class:`int` + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. + bitrate: :class:`int` + The channel's preferred audio bitrate in bits per second. + user_limit: :class:`int` + The channel's limit for number of members that can be in a stage channel. + rtc_region: Optional[:class:`VoiceRegion`] + The region for the stage channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the stage channel's participants. + + .. versionadded:: 2.0 + """ + + __slots__ = ('topic',) + + def __repr__(self) -> str: + attrs = [ + ('id', self.id), + ('name', self.name), + ('topic', self.topic), + ('rtc_region', self.rtc_region), + ('position', self.position), + ('bitrate', self.bitrate), + ('video_quality_mode', self.video_quality_mode), + ('user_limit', self.user_limit), + ('category_id', self.category_id), + ] + joined = ' '.join('%s=%r' % t for t in attrs) + return f'<{self.__class__.__name__} {joined}>' + + def _update(self, guild: Guild, data: StageChannelPayload) -> None: + super()._update(guild, data) + self.topic = data.get('topic') + + @property + def requesting_to_speak(self) -> List[Member]: + """List[:class:`Member`]: A list of members who are requesting to speak in the stage channel.""" + return [member for member in self.members if member.voice and member.voice.requested_to_speak_at is not None] + + @property + def speakers(self) -> List[Member]: + """List[:class:`Member`]: A list of members who have been permitted to speak in the stage channel. + + .. versionadded:: 2.0 + """ + return [ + member + for member in self.members + if member.voice and not member.voice.suppress and member.voice.requested_to_speak_at is None + ] + + @property + def listeners(self) -> List[Member]: + """List[:class:`Member`]: A list of members who are listening in the stage channel. + + .. versionadded:: 2.0 + """ + return [member for member in self.members if member.voice and member.voice.suppress] + + @property + def moderators(self) -> List[Member]: + """List[:class:`Member`]: A list of members who are moderating the stage channel. + + .. versionadded:: 2.0 + """ + required_permissions = Permissions.stage_moderator() + return [member for member in self.members if self.permissions_for(member) >= required_permissions] + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.stage_voice + + @utils.copy_doc(discord.abc.GuildChannel.clone) + async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> StageChannel: + return await self._clone_impl({}, name=name, reason=reason) + + @property + def instance(self) -> Optional[StageInstance]: + """Optional[:class:`StageInstance`]: The running stage instance of the stage channel. + + .. versionadded:: 2.0 + """ + return utils.get(self.guild.stage_instances, channel_id=self.id) + + async def create_instance( + self, *, topic: str, privacy_level: StagePrivacyLevel = MISSING, reason: Optional[str] = None + ) -> StageInstance: + """|coro| + + Create a stage instance. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionadded:: 2.0 + + Parameters + ----------- + topic: :class:`str` + The stage instance's topic. + privacy_level: :class:`StagePrivacyLevel` + The stage instance's privacy level. Defaults to :attr:`StagePrivacyLevel.guild_only`. + reason: :class:`str` + The reason the stage instance was created. Shows up on the audit log. + + Raises + ------ + InvalidArgument + If the ``privacy_level`` parameter is not the proper type. + Forbidden + You do not have permissions to create a stage instance. + HTTPException + Creating a stage instance failed. + + Returns + -------- + :class:`StageInstance` + The newly created stage instance. + """ + + payload: Dict[str, Any] = {'channel_id': self.id, 'topic': topic} + + if privacy_level is not MISSING: + if not isinstance(privacy_level, StagePrivacyLevel): + raise InvalidArgument('privacy_level field must be of type PrivacyLevel') + + payload['privacy_level'] = privacy_level.value + + data = await self._state.http.create_stage_instance(**payload, reason=reason) + return StageInstance(guild=self.guild, state=self._state, data=data) + + async def fetch_instance(self) -> StageInstance: + """|coro| + + Gets the running :class:`StageInstance`. + + .. versionadded:: 2.0 + + Raises + ------- + :exc:`.NotFound` + The stage instance or channel could not be found. + :exc:`.HTTPException` + Getting the stage instance failed. + + Returns + -------- + :class:`StageInstance` + The stage instance. + """ + data = await self._state.http.get_stage_instance(self.id) + return StageInstance(guild=self.guild, state=self._state, data=data) + + @overload + async def edit( + self, + *, + name: str = ..., + topic: Optional[str] = ..., + position: int = ..., + sync_permissions: int = ..., + category: Optional[CategoryChannel] = ..., + overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + rtc_region: Optional[VoiceRegion] = ..., + video_quality_mode: VideoQualityMode = ..., + reason: Optional[str] = ..., + ) -> Optional[StageChannel]: + ... + + @overload + async def edit(self) -> Optional[StageChannel]: + ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 2.0 + The ``topic`` parameter must now be set via :attr:`create_instance`. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + Parameters + ---------- + name: :class:`str` + The new channel's name. + position: :class:`int` + The new channel's position. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing + category. Defaults to ``False``. + category: Optional[:class:`CategoryChannel`] + The new category for this channel. Can be ``None`` to remove the + category. + reason: Optional[:class:`str`] + The reason for editing this channel. Shows up on the audit log. + overwrites: :class:`Mapping` + A :class:`Mapping` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply to the channel. + rtc_region: Optional[:class:`VoiceRegion`] + The new region for the stage channel's voice communication. + A value of ``None`` indicates automatic voice region detection. + video_quality_mode: :class:`VideoQualityMode` + The camera video quality for the stage channel's participants. + + .. versionadded:: 2.0 + + Raises + ------ + InvalidArgument + If the permission overwrite information is not in proper form. + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + + Returns + -------- + Optional[:class:`.StageChannel`] + The newly edited stage channel. If the edit was only positional + then ``None`` is returned instead. + """ + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + + +class CategoryChannel(discord.abc.GuildChannel, Hashable): + """Represents a Discord channel category. + + These are useful to group channels to logical compartments. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the category's hash. + + .. describe:: str(x) + + Returns the category's name. + + Attributes + ----------- + name: :class:`str` + The category name. + guild: :class:`Guild` + The guild the category belongs to. + id: :class:`int` + The category channel ID. + position: :class:`int` + The position in the category list. This is a number that starts at 0. e.g. the + top category is position 0. + nsfw: :class:`bool` + If the channel is marked as "not safe for work". + + .. note:: + + To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. + """ + + __slots__ = ('name', 'id', 'guild', 'nsfw', '_state', 'position', '_overwrites', 'category_id') + + def __init__(self, *, state: ConnectionState, guild: Guild, data: CategoryChannelPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self._update(guild, data) + + def __repr__(self) -> str: + return f'' + + def _update(self, guild: Guild, data: CategoryChannelPayload) -> None: + self.guild: Guild = guild + self.name: str = data['name'] + self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id') + self.nsfw: bool = data.get('nsfw', False) + self.position: int = data['position'] + self._fill_overwrites(data) + + @property + def _sorting_bucket(self) -> int: + return ChannelType.category.value + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.category + + def is_nsfw(self) -> bool: + """:class:`bool`: Checks if the category is NSFW.""" + return self.nsfw + + @utils.copy_doc(discord.abc.GuildChannel.clone) + async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> CategoryChannel: + return await self._clone_impl({'nsfw': self.nsfw}, name=name, reason=reason) + + @overload + async def edit( + self, + *, + name: str = ..., + position: int = ..., + nsfw: bool = ..., + overwrites: Mapping[Union[Role, Member], PermissionOverwrite] = ..., + reason: Optional[str] = ..., + ) -> Optional[CategoryChannel]: + ... + + @overload + async def edit(self) -> Optional[CategoryChannel]: + ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 1.3 + The ``overwrites`` keyword-only parameter was added. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + Parameters + ---------- + name: :class:`str` + The new category's name. + position: :class:`int` + The new category's position. + nsfw: :class:`bool` + To mark the category as NSFW or not. + reason: Optional[:class:`str`] + The reason for editing this category. Shows up on the audit log. + overwrites: :class:`Mapping` + A :class:`Mapping` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply to the channel. + + Raises + ------ + InvalidArgument + If position is less than 0 or greater than the number of categories. + Forbidden + You do not have permissions to edit the category. + HTTPException + Editing the category failed. + + Returns + -------- + Optional[:class:`.CategoryChannel`] + The newly edited category channel. If the edit was only positional + then ``None`` is returned instead. + """ + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + + @utils.copy_doc(discord.abc.GuildChannel.move) + async def move(self, **kwargs): + kwargs.pop('category', None) + await super().move(**kwargs) + + @property + def channels(self) -> List[GuildChannelType]: + """List[:class:`abc.GuildChannel`]: Returns the channels that are under this category. + + These are sorted by the official Discord UI, which places voice channels below the text channels. + """ + + def comparator(channel): + return (not isinstance(channel, TextChannel), channel.position) + + ret = [c for c in self.guild.channels if c.category_id == self.id] + ret.sort(key=comparator) + return ret + + @property + def text_channels(self) -> List[TextChannel]: + """List[:class:`TextChannel`]: Returns the text channels that are under this category.""" + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, TextChannel)] + ret.sort(key=lambda c: (c.position, c.id)) + return ret + + @property + def voice_channels(self) -> List[VoiceChannel]: + """List[:class:`VoiceChannel`]: Returns the voice channels that are under this category.""" + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, VoiceChannel)] + ret.sort(key=lambda c: (c.position, c.id)) + return ret + + @property + def stage_channels(self) -> List[StageChannel]: + """List[:class:`StageChannel`]: Returns the stage channels that are under this category. + + .. versionadded:: 1.7 + """ + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, StageChannel)] + ret.sort(key=lambda c: (c.position, c.id)) + return ret + + async def create_text_channel(self, name: str, **options: Any) -> TextChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_text_channel` to create a :class:`TextChannel` in the category. + + Returns + ------- + :class:`TextChannel` + The channel that was just created. + """ + return await self.guild.create_text_channel(name, category=self, **options) + + async def create_voice_channel(self, name: str, **options: Any) -> VoiceChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_voice_channel` to create a :class:`VoiceChannel` in the category. + + Returns + ------- + :class:`VoiceChannel` + The channel that was just created. + """ + return await self.guild.create_voice_channel(name, category=self, **options) + + async def create_stage_channel(self, name: str, **options: Any) -> StageChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_stage_channel` to create a :class:`StageChannel` in the category. + + .. versionadded:: 1.7 + + Returns + ------- + :class:`StageChannel` + The channel that was just created. + """ + return await self.guild.create_stage_channel(name, category=self, **options) + + +class StoreChannel(discord.abc.GuildChannel, Hashable): + """Represents a Discord guild store channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns the channel's name. + + Attributes + ----------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + category_id: :class:`int` + The category channel ID this channel belongs to. + position: :class:`int` + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. + nsfw: :class:`bool` + If the channel is marked as "not safe for work". + + .. note:: + + To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. + """ + + __slots__ = ( + 'name', + 'id', + 'guild', + '_state', + 'nsfw', + 'category_id', + 'position', + '_overwrites', + ) + + def __init__(self, *, state: ConnectionState, guild: Guild, data: StoreChannelPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self._update(guild, data) + + def __repr__(self) -> str: + return f'' + + def _update(self, guild: Guild, data: StoreChannelPayload) -> None: + self.guild: Guild = guild + self.name: str = data['name'] + self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id') + self.position: int = data['position'] + self.nsfw: bool = data.get('nsfw', False) + self._fill_overwrites(data) + + @property + def _sorting_bucket(self) -> int: + return ChannelType.text.value + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.store + + @utils.copy_doc(discord.abc.GuildChannel.permissions_for) + def permissions_for(self, obj: Union[Member, Role], /) -> Permissions: + base = super().permissions_for(obj) + + # store channels do not have voice related permissions + denied = Permissions.voice() + base.value &= ~denied.value + return base + + def is_nsfw(self) -> bool: + """:class:`bool`: Checks if the channel is NSFW.""" + return self.nsfw + + @utils.copy_doc(discord.abc.GuildChannel.clone) + async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> StoreChannel: + return await self._clone_impl({'nsfw': self.nsfw}, name=name, reason=reason) + + @overload + async def edit( + self, + *, + name: str = ..., + position: int = ..., + nsfw: bool = ..., + sync_permissions: bool = ..., + category: Optional[CategoryChannel], + reason: Optional[str], + overwrites: Mapping[Union[Role, Member], PermissionOverwrite], + ) -> Optional[StoreChannel]: + ... + + @overload + async def edit(self) -> Optional[StoreChannel]: + ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + Parameters + ---------- + name: :class:`str` + The new channel name. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + To mark the channel as NSFW or not. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing + category. Defaults to ``False``. + category: Optional[:class:`CategoryChannel`] + The new category for this channel. Can be ``None`` to remove the + category. + reason: Optional[:class:`str`] + The reason for editing this channel. Shows up on the audit log. + overwrites: :class:`Mapping` + A :class:`Mapping` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply to the channel. + + .. versionadded:: 1.3 + + Raises + ------ + InvalidArgument + If position is less than 0 or greater than the number of channels, or if + the permission overwrite information is not in proper form. + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + + Returns + -------- + Optional[:class:`.StoreChannel`] + The newly edited store channel. If the edit was only positional + then ``None`` is returned instead. + """ + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + + +DMC = TypeVar('DMC', bound='DMChannel') + + +class DMChannel(discord.abc.Messageable, Hashable): + """Represents a Discord direct message channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns a string representation of the channel + + Attributes + ---------- + recipient: Optional[:class:`User`] + The user you are participating with in the direct message channel. + If this channel is received through the gateway, the recipient information + may not be always available. + me: :class:`ClientUser` + The user presenting yourself. + id: :class:`int` + The direct message channel ID. + """ + + __slots__ = ('id', 'recipient', 'me', '_state') + + def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload): + self._state: ConnectionState = state + self.recipient: Optional[User] = state.store_user(data['recipients'][0]) + self.me: ClientUser = me + self.id: int = int(data['id']) + + async def _get_channel(self): + return self + + def __str__(self) -> str: + if self.recipient: + return f'Direct Message with {self.recipient}' + return 'Direct Message with Unknown User' + + def __repr__(self) -> str: + return f'' + + @classmethod + def _from_message(cls: Type[DMC], state: ConnectionState, channel_id: int) -> DMC: + self: DMC = cls.__new__(cls) + self._state = state + self.id = channel_id + self.recipient = None + # state.user won't be None here + self.me = state.user # type: ignore + return self + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.private + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the direct message channel's creation time in UTC.""" + return utils.snowflake_time(self.id) + + def permissions_for(self, obj: Any = None, /) -> Permissions: + """Handles permission resolution for a :class:`User`. + + This function is there for compatibility with other channel types. + + Actual direct messages do not really have the concept of permissions. + + This returns all the Text related permissions set to ``True`` except: + + - :attr:`~Permissions.send_tts_messages`: You cannot send TTS messages in a DM. + - :attr:`~Permissions.manage_messages`: You cannot delete others messages in a DM. + + Parameters + ----------- + obj: :class:`User` + The user to check permissions for. This parameter is ignored + but kept for compatibility with other ``permissions_for`` methods. + + Returns + -------- + :class:`Permissions` + The resolved permissions. + """ + + base = Permissions.text() + base.read_messages = True + base.send_tts_messages = False + base.manage_messages = False + return base + + 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:: 1.6 + + 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) + + +class GroupChannel(discord.abc.Messageable, Hashable): + """Represents a Discord group channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns a string representation of the channel + + Attributes + ---------- + recipients: List[:class:`User`] + The users you are participating with in the group channel. + me: :class:`ClientUser` + The user presenting yourself. + id: :class:`int` + The group channel ID. + owner: Optional[:class:`User`] + The user that owns the group channel. + owner_id: :class:`int` + The owner ID that owns the group channel. + + .. versionadded:: 2.0 + name: Optional[:class:`str`] + The group channel's name if provided. + """ + + __slots__ = ('id', 'recipients', 'owner_id', 'owner', '_icon', 'name', 'me', '_state') + + def __init__(self, *, me: ClientUser, state: ConnectionState, data: GroupChannelPayload): + self._state: ConnectionState = state + self.id: int = int(data['id']) + self.me: ClientUser = me + self._update_group(data) + + def _update_group(self, data: GroupChannelPayload) -> None: + self.owner_id: Optional[int] = utils._get_as_snowflake(data, 'owner_id') + self._icon: Optional[str] = data.get('icon') + self.name: Optional[str] = data.get('name') + self.recipients: List[User] = [self._state.store_user(u) for u in data.get('recipients', [])] + + self.owner: Optional[BaseUser] + if self.owner_id == self.me.id: + self.owner = self.me + else: + self.owner = utils.find(lambda u: u.id == self.owner_id, self.recipients) + + async def _get_channel(self): + return self + + def __str__(self) -> str: + if self.name: + return self.name + + if len(self.recipients) == 0: + return 'Unnamed' + + return ', '.join(map(lambda x: x.name, self.recipients)) + + def __repr__(self) -> str: + return f'' + + @property + def type(self) -> ChannelType: + """:class:`ChannelType`: The channel's Discord type.""" + return ChannelType.group + + @property + def icon(self) -> Optional[Asset]: + """Optional[:class:`Asset`]: Returns the channel's icon asset if available.""" + if self._icon is None: + return None + return Asset._from_icon(self._state, self.id, self._icon, path='channel') + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the channel's creation time in UTC.""" + return utils.snowflake_time(self.id) + + def permissions_for(self, obj: Snowflake, /) -> Permissions: + """Handles permission resolution for a :class:`User`. + + This function is there for compatibility with other channel types. + + Actual direct messages do not really have the concept of permissions. + + This returns all the Text related permissions set to ``True`` except: + + - :attr:`~Permissions.send_tts_messages`: You cannot send TTS messages in a DM. + - :attr:`~Permissions.manage_messages`: You cannot delete others messages in a DM. + + This also checks the kick_members permission if the user is the owner. + + Parameters + ----------- + obj: :class:`~discord.abc.Snowflake` + The user to check permissions for. + + Returns + -------- + :class:`Permissions` + The resolved permissions for the user. + """ + + base = Permissions.text() + base.read_messages = True + base.send_tts_messages = False + base.manage_messages = False + base.mention_everyone = True + + if obj.id == self.owner_id: + base.kick_members = True + + return base + + async def leave(self) -> None: + """|coro| + + Leave the group. + + If you are the only one in the group, this deletes it as well. + + Raises + ------- + HTTPException + Leaving the group failed. + """ + + await self._state.http.leave_group(self.id) + + +class PartialMessageable(discord.abc.Messageable, Hashable): + """Represents a partial messageable to aid with working messageable channels when + only a channel ID are present. + + The only way to construct this class is through :meth:`Client.get_partial_messageable`. + + Note that this class is trimmed down and has no rich attributes. + + .. versionadded:: 2.0 + + .. container:: operations + + .. describe:: x == y + + Checks if two partial messageables are equal. + + .. describe:: x != y + + Checks if two partial messageables are not equal. + + .. describe:: hash(x) + + Returns the partial messageable's hash. + + Attributes + ----------- + id: :class:`int` + The channel ID associated with this partial messageable. + type: Optional[:class:`ChannelType`] + The channel type associated with this partial messageable, if given. + """ + + def __init__(self, state: ConnectionState, id: int, type: Optional[ChannelType] = None): + self._state: ConnectionState = state + self._channel: Object = Object(id=id) + self.id: int = id + self.type: Optional[ChannelType] = type + + async def _get_channel(self) -> Object: + return self._channel + + 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. + + 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 _guild_channel_factory(channel_type: int): + value = try_enum(ChannelType, channel_type) + if value is ChannelType.text: + return TextChannel, value + elif value is ChannelType.voice: + return VoiceChannel, value + elif value is ChannelType.category: + return CategoryChannel, value + elif value is ChannelType.news: + return TextChannel, value + elif value is ChannelType.store: + return StoreChannel, value + elif value is ChannelType.stage_voice: + return StageChannel, value + else: + return None, value + + +def _channel_factory(channel_type: int): + cls, value = _guild_channel_factory(channel_type) + if value is ChannelType.private: + return DMChannel, value + elif value is ChannelType.group: + return GroupChannel, value + else: + return cls, value + + +def _threaded_channel_factory(channel_type: int): + cls, value = _channel_factory(channel_type) + if value in (ChannelType.private_thread, ChannelType.public_thread, ChannelType.news_thread): + return Thread, value + return cls, value + + +def _threaded_guild_channel_factory(channel_type: int): + cls, value = _guild_channel_factory(channel_type) + if value in (ChannelType.private_thread, ChannelType.public_thread, ChannelType.news_thread): + return Thread, value + return cls, value diff --git a/build/lib/discord/client.py b/build/lib/discord/client.py new file mode 100644 index 0000000000..3c05ad3f40 --- /dev/null +++ b/build/lib/discord/client.py @@ -0,0 +1,1653 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import asyncio +import logging +import signal +import sys +import traceback +from typing import Any, Callable, Coroutine, Dict, Generator, List, Optional, Sequence, TYPE_CHECKING, Tuple, TypeVar, Union + +import aiohttp + +from .user import User, ClientUser +from .invite import Invite +from .template import Template +from .widget import Widget +from .guild import Guild +from .emoji import Emoji +from .channel import _threaded_channel_factory, PartialMessageable +from .enums import ChannelType +from .mentions import AllowedMentions +from .errors import * +from .enums import Status, VoiceRegion +from .flags import ApplicationFlags, Intents +from .gateway import * +from .activity import ActivityTypes, BaseActivity, create_activity +from .voice_client import VoiceClient +from .http import HTTPClient +from .state import ConnectionState +from . import utils +from .utils import MISSING +from .object import Object +from .backoff import ExponentialBackoff +from .webhook import Webhook +from .iterators import GuildIterator +from .appinfo import AppInfo +from .ui.view import View +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__ = ( + 'Client', +) + +Coro = TypeVar('Coro', bound=Callable[..., Coroutine[Any, Any, Any]]) + + +_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()} + + if not tasks: + return + + _log.info('Cleaning up after %d tasks.', len(tasks)) + for task in tasks: + task.cancel() + + loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) + _log.info('All tasks finished cancelling.') + + for task in tasks: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler({ + 'message': 'Unhandled exception during Client.run shutdown.', + 'exception': task.exception(), + 'task': task + }) + +def _cleanup_loop(loop: asyncio.AbstractEventLoop) -> None: + try: + _cancel_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + finally: + _log.info('Closing the event loop.') + loop.close() + +class Client: + r"""Represents a client connection that connects to Discord. + This class is used to interact with the Discord WebSocket and API. + + A number of options can be passed to the :class:`Client`. + + Parameters + ----------- + max_messages: Optional[:class:`int`] + The maximum number of messages to store in the internal message cache. + This defaults to ``1000``. Passing in ``None`` disables the message cache. + + .. versionchanged:: 1.3 + Allow disabling the message cache and change the default size to ``1000``. + loop: Optional[:class:`asyncio.AbstractEventLoop`] + The :class:`asyncio.AbstractEventLoop` to use for asynchronous operations. + Defaults to ``None``, in which case the default event loop is used via + :func:`asyncio.get_event_loop()`. + connector: Optional[:class:`aiohttp.BaseConnector`] + The connector to use for connection pooling. + proxy: Optional[:class:`str`] + Proxy URL. + proxy_auth: Optional[:class:`aiohttp.BasicAuth`] + An object that represents proxy HTTP Basic Authorization. + shard_id: Optional[:class:`int`] + Integer starting at ``0`` and less than :attr:`.shard_count`. + shard_count: Optional[:class:`int`] + The total number of shards. + application_id: :class:`int` + The client's application ID. + intents: :class:`Intents` + 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. + If not given, defaults to a regularly constructed :class:`Intents` class. + + .. versionadded:: 1.5 + member_cache_flags: :class:`MemberCacheFlags` + Allows for finer control over how the library caches members. + If not given, defaults to cache as much as possible with the + currently selected intents. + + .. versionadded:: 1.5 + chunk_guilds_at_startup: :class:`bool` + Indicates if :func:`.on_ready` should be delayed to chunk all guilds + at start-up if necessary. This operation is incredibly slow for large + amounts of guilds. The default is ``True`` if :attr:`Intents.members` + is ``True``. + + .. versionadded:: 1.5 + status: Optional[:class:`.Status`] + A status to start your presence with upon logging on to Discord. + activity: Optional[:class:`.BaseActivity`] + An activity to start your presence with upon logging on to Discord. + allowed_mentions: Optional[:class:`AllowedMentions`] + Control how the client handles mentions by default on every message sent. + + .. versionadded:: 1.4 + heartbeat_timeout: :class:`float` + The maximum numbers of seconds before timing out and restarting the + WebSocket in the case of not receiving a HEARTBEAT_ACK. Useful if + processing the initial packets take too long to the point of disconnecting + you. The default timeout is 60 seconds. + guild_ready_timeout: :class:`float` + The maximum number of seconds to wait for the GUILD_CREATE stream to end before + preparing the member cache and firing READY. The default timeout is 2 seconds. + + .. versionadded:: 1.4 + assume_unsync_clock: :class:`bool` + Whether to assume the system clock is unsynced. This applies to the ratelimit handling + code. If this is set to ``True``, the default, then the library uses the time to reset + a rate limit bucket given by Discord. If this is ``False`` then your system clock is + used to calculate how long to sleep for. If this is set to ``False`` it is recommended to + sync your system clock to Google's NTP server. + + .. 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 + ----------- + ws + The websocket gateway the client is currently connected to. Could be ``None``. + loop: :class:`asyncio.AbstractEventLoop` + The event loop that the client uses for asynchronous operations. + """ + def __init__( + self, + *, + loop: Optional[asyncio.AbstractEventLoop] = None, + **options: Any, + ): + # self.ws is set in the connect method + self.ws: DiscordWebSocket = None # type: ignore + self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop + self._listeners: Dict[str, List[Tuple[asyncio.Future, Callable[..., bool]]]] = {} + self.shard_id: Optional[int] = options.get('shard_id') + self.shard_count: Optional[int] = options.get('shard_count') + + 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 + } + + self._hooks: Dict[str, Callable] = { + 'before_identify': self._call_before_identify_hook + } + + 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._closed: bool = False + self._ready: asyncio.Event = asyncio.Event() + self._connection._get_websocket = self._get_websocket + self._connection._get_client = lambda: self + + if VoiceClient.warn_nacl: + VoiceClient.warn_nacl = False + _log.warning("PyNaCl is not installed, voice will NOT be supported") + + # internals + + def _get_websocket(self, guild_id: Optional[int] = None, *, shard_id: Optional[int] = None) -> DiscordWebSocket: + return self.ws + + def _get_state(self, **options: Any) -> ConnectionState: + return ConnectionState(dispatch=self.dispatch, handlers=self._handlers, + hooks=self._hooks, http=self.http, loop=self.loop, **options) + + def _handle_ready(self) -> None: + self._ready.set() + + @property + def latency(self) -> float: + """:class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds. + + This could be referred to as the Discord WebSocket protocol latency. + """ + ws = self.ws + return float('nan') if not ws else ws.latency + + def is_ws_ratelimited(self) -> bool: + """:class:`bool`: Whether the websocket is currently rate limited. + + This can be useful to know when deciding whether you should query members + using HTTP or via the gateway. + + .. versionadded:: 1.6 + """ + if self.ws: + return self.ws.is_ratelimited() + return False + + @property + def user(self) -> Optional[ClientUser]: + """Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in.""" + return self._connection.user + + @property + def guilds(self) -> List[Guild]: + """List[:class:`.Guild`]: The guilds that the connected client is a member of.""" + return self._connection.guilds + + @property + def emojis(self) -> List[Emoji]: + """List[:class:`.Emoji`]: The emojis that the connected client has.""" + return self._connection.emojis + + @property + 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. + + .. versionadded:: 1.1 + """ + return utils.SequenceProxy(self._connection._messages or []) + + @property + def private_channels(self) -> List[PrivateChannel]: + """List[:class:`.abc.PrivateChannel`]: The private channels that the connected client is participating on. + + .. note:: + + This returns only up to 128 most recent private channels due to an internal working + on how Discord deals with private channels. + """ + return self._connection.private_channels + + @property + def voice_clients(self) -> List[VoiceProtocol]: + """List[:class:`.VoiceProtocol`]: Represents a list of voice connections. + + These are usually :class:`.VoiceClient` instances. + """ + return self._connection.voice_clients + + @property + def application_id(self) -> Optional[int]: + """Optional[:class:`int`]: The client's application ID. + + If this is not passed via ``__init__`` then this is retrieved + through the gateway when an event contains the data. Usually + after :func:`~discord.on_connect` is called. + + .. versionadded:: 2.0 + """ + return self._connection.application_id + + @property + def application_flags(self) -> ApplicationFlags: + """:class:`~discord.ApplicationFlags`: The client's application flags. + + .. versionadded:: 2.0 + """ + return self._connection.application_flags # type: ignore + + def is_ready(self) -> bool: + """:class:`bool`: Specifies if the client's internal cache is ready for use.""" + return self._ready.is_set() + + async def _run_event(self, coro: Callable[..., Coroutine[Any, Any, Any]], event_name: str, *args: Any, **kwargs: Any) -> None: + try: + await coro(*args, **kwargs) + except asyncio.CancelledError: + pass + except Exception: + try: + await self.on_error(event_name, *args, **kwargs) + except asyncio.CancelledError: + pass + + 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) + # Schedules the task + return asyncio.create_task(wrapped, name=f'pycord: {event_name}') + + def dispatch(self, event: str, *args: Any, **kwargs: Any) -> None: + _log.debug('Dispatching event %s', event) + method = 'on_' + event + + listeners = self._listeners.get(event) + if listeners: + removed = [] + for i, (future, condition) in enumerate(listeners): + if future.cancelled(): + removed.append(i) + continue + + try: + result = condition(*args) + except Exception as exc: + future.set_exception(exc) + removed.append(i) + else: + if result: + if len(args) == 0: + future.set_result(None) + elif len(args) == 1: + future.set_result(args[0]) + else: + future.set_result(args) + removed.append(i) + + if len(removed) == len(listeners): + self._listeners.pop(event) + else: + for idx in reversed(removed): + del listeners[idx] + + try: + coro = getattr(self, method) + except AttributeError: + pass + else: + self._schedule_event(coro, method, *args, **kwargs) + + async def on_error(self, event_method: str, *args: Any, **kwargs: Any) -> None: + """|coro| + + The default error handler provided by the client. + + By default this prints to :data:`sys.stderr` however it could be + overridden to have a different implementation. + Check :func:`~discord.on_error` for more details. + """ + print(f'Ignoring exception in {event_method}', file=sys.stderr) + traceback.print_exc() + + # hooks + + 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. + # It allows the library to have its own hook without stepping on the + # toes of those who need to override their own hook. + await self.before_identify_hook(shard_id, initial=initial) + + async def before_identify_hook(self, shard_id: Optional[int], *, initial: bool = False) -> None: + """|coro| + + A hook that is called before IDENTIFYing a session. This is useful + if you wish to have more control over the synchronization of multiple + IDENTIFYing clients. + + The default implementation sleeps for 5 seconds. + + .. versionadded:: 1.4 + + Parameters + ------------ + shard_id: :class:`int` + The shard ID that requested being IDENTIFY'd + initial: :class:`bool` + Whether this IDENTIFY is the first initial IDENTIFY. + """ + + if not initial: + await asyncio.sleep(5.0) + + # login state management + + async def login(self, token: str) -> None: + """|coro| + + Logs in the client with the specified credentials. + + + Parameters + ----------- + token: :class:`str` + The authentication token. Do not prefix this token with + anything as the library will do it for you. + + Raises + ------ + TypeError + The token was in invalid type. + :exc:`.LoginFailure` + The wrong credentials are passed. + :exc:`.HTTPException` + An unknown HTTP related error occurred, + usually when it isn't 200 or the known incorrect credentials + passing status code. + """ + if not isinstance(token, str): + raise TypeError(f"token must be of type str, not {token.__class__.__name__}") + + _log.info('logging in using static token') + + 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| + + Creates a websocket connection and lets the websocket listen + to messages from Discord. This is a loop that runs the entire + event system and miscellaneous aspects of the library. Control + is not resumed until the WebSocket connection is terminated. + + Parameters + ----------- + reconnect: :class:`bool` + If we should attempt reconnecting, either due to internet + failure or a specific failure on Discord's part. Certain + disconnects that lead to bad state will not be handled (such as + invalid sharding payloads or bad tokens). + + Raises + ------- + :exc:`.GatewayNotFound` + If the gateway to connect to Discord is not found. Usually if this + is thrown then there is a Discord API outage. + :exc:`.ConnectionClosed` + The websocket connection has been terminated. + """ + + backoff = ExponentialBackoff() + ws_params = { + 'initial': True, + 'shard_id': self.shard_id, + } + while not self.is_closed(): + try: + coro = DiscordWebSocket.from_client(self, **ws_params) + self.ws = await asyncio.wait_for(coro, timeout=60.0) + ws_params['initial'] = False + while True: + await self.ws.poll_event() + except ReconnectWebSocket as e: + _log.info('Got a request to %s the websocket.', e.op) + self.dispatch('disconnect') + ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id) + continue + except (OSError, + HTTPException, + GatewayNotFound, + ConnectionClosed, + aiohttp.ClientError, + asyncio.TimeoutError) as exc: + + self.dispatch('disconnect') + if not reconnect: + await self.close() + if isinstance(exc, ConnectionClosed) and exc.code == 1000: + # clean close, don't re-raise this + return + raise + + if self.is_closed(): + return + + # If we get connection reset by peer then try to RESUME + if isinstance(exc, OSError) and exc.errno in (54, 10054): + ws_params.update(sequence=self.ws.sequence, initial=False, resume=True, session=self.ws.session_id) + continue + + # We should only get this when an unhandled close code happens, + # such as a clean disconnect (1000) or a bad state (bad token, no sharding, etc) + # sometimes, discord sends us 1000 for unknown reasons so we should reconnect + # regardless and rely on is_closed instead + if isinstance(exc, ConnectionClosed): + if exc.code == 4014: + raise PrivilegedIntentsRequired(exc.shard_id) from None + if exc.code != 1000: + await self.close() + raise + + retry = backoff.delay() + _log.exception("Attempting a reconnect in %.2fs", retry) + await asyncio.sleep(retry) + # Always try to RESUME the connection + # If the connection is not RESUME-able then the gateway will invalidate the session. + # This is apparently what the official Discord client does. + ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id) + + async def close(self) -> None: + """|coro| + + Closes the connection to Discord. + """ + if self._closed: + return + + self._closed = True + + for voice in self.voice_clients: + try: + await voice.disconnect(force=True) + except Exception: + # if an error happens during disconnects, disregard it. + pass + + if self.ws is not None and self.ws.open: + await self.ws.close(code=1000) + + await self.http.close() + self._ready.clear() + + def clear(self) -> None: + """Clears the internal state of the bot. + + After this, the bot can be considered "re-opened", i.e. :meth:`is_closed` + and :meth:`is_ready` both return ``False`` along with the bot's internal + cache cleared. + """ + self._closed = False + self._ready.clear() + self._connection.clear() + self.http.recreate() + + async def start(self, token: str, *, reconnect: bool = True) -> None: + """|coro| + + A shorthand coroutine for :meth:`login` + :meth:`connect`. + + Raises + ------- + TypeError + An unexpected keyword argument was received. + """ + await self.login(token) + await self.connect(reconnect=reconnect) + + def run(self, *args: Any, **kwargs: Any) -> None: + """A blocking call that abstracts away the event loop + initialisation from you. + + If you want more control over the event loop then this + function should not be used. Use :meth:`start` coroutine + or :meth:`connect` + :meth:`login`. + + Roughly Equivalent to: :: + + try: + loop.run_until_complete(start(*args, **kwargs)) + except KeyboardInterrupt: + loop.run_until_complete(close()) + # cancel all tasks lingering + finally: + loop.close() + + .. warning:: + + This function must be the last function to call due to the fact that it + is blocking. That means that registration of events or anything being + called after this function call will not execute until it returns. + """ + loop = self.loop + + try: + loop.add_signal_handler(signal.SIGINT, lambda: loop.stop()) + loop.add_signal_handler(signal.SIGTERM, lambda: loop.stop()) + except NotImplementedError: + pass + + async def runner(): + try: + await self.start(*args, **kwargs) + finally: + if not self.is_closed(): + await self.close() + + def stop_loop_on_completion(f): + loop.stop() + + future = asyncio.ensure_future(runner(), loop=loop) + future.add_done_callback(stop_loop_on_completion) + try: + loop.run_forever() + except KeyboardInterrupt: + _log.info('Received signal to terminate bot and event loop.') + finally: + future.remove_done_callback(stop_loop_on_completion) + _log.info('Cleaning up tasks.') + _cleanup_loop(loop) + + if not future.cancelled(): + try: + return future.result() + except KeyboardInterrupt: + # I am unsure why this gets raised here but suppress it anyway + return None + + # properties + + def is_closed(self) -> bool: + """:class:`bool`: Indicates if the websocket connection is closed.""" + return self._closed + + @property + def activity(self) -> Optional[ActivityTypes]: + """Optional[:class:`.BaseActivity`]: The activity being used upon + logging in. + """ + return create_activity(self._connection._activity) + + @activity.setter + def activity(self, value: Optional[ActivityTypes]) -> None: + if value is None: + self._connection._activity = None + elif isinstance(value, BaseActivity): + # ConnectionState._activity is typehinted as ActivityPayload, we're passing Dict[str, Any] + self._connection._activity = value.to_dict() # type: ignore + else: + raise TypeError('activity must derive from BaseActivity.') + + @property + def status(self): + """:class:`.Status`: + The status being used upon logging on to Discord. + + .. versionadded: 2.0 + """ + if self._connection._status in set(state.value for state in Status): + return Status(self._connection._status) + return Status.online + + @status.setter + def status(self, value): + if value is Status.offline: + self._connection._status = 'invisible' + elif isinstance(value, Status): + self._connection._status = str(value) + else: + raise TypeError('status must derive from Status.') + + @property + def allowed_mentions(self) -> Optional[AllowedMentions]: + """Optional[:class:`~discord.AllowedMentions`]: The allowed mention configuration. + + .. versionadded:: 1.4 + """ + return self._connection.allowed_mentions + + @allowed_mentions.setter + def allowed_mentions(self, value: Optional[AllowedMentions]) -> None: + if value is None or isinstance(value, AllowedMentions): + self._connection.allowed_mentions = value + else: + raise TypeError(f'allowed_mentions must be AllowedMentions not {value.__class__!r}') + + @property + def intents(self) -> Intents: + """:class:`~discord.Intents`: The intents configured for this connection. + + .. versionadded:: 1.5 + """ + return self._connection.intents + + # helpers/getters + + @property + def users(self) -> List[User]: + """List[:class:`~discord.User`]: Returns a list of all the users the bot can see.""" + return list(self._connection._users.values()) + + def get_channel(self, id: int, /) -> Optional[Union[GuildChannel, Thread, PrivateChannel]]: + """Returns a channel or thread with the given ID. + + Parameters + ----------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[Union[:class:`.abc.GuildChannel`, :class:`.Thread`, :class:`.abc.PrivateChannel`]] + The returned channel or ``None`` if not found. + """ + return self._connection.get_channel(id) + + 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. + + .. versionadded:: 2.0 + + Parameters + ----------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`.StageInstance`] + The returns stage instance of ``None`` if not found. + """ + from .channel import StageChannel + + channel = self._connection.get_channel(id) + + if isinstance(channel, StageChannel): + return channel.instance + + def get_guild(self, id: int, /) -> Optional[Guild]: + """Returns a guild with the given ID. + + Parameters + ----------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`.Guild`] + The guild or ``None`` if not found. + """ + return self._connection._get_guild(id) + + def get_user(self, id: int, /) -> Optional[User]: + """Returns a user with the given ID. + + Parameters + ----------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`~discord.User`] + The user or ``None`` if not found. + """ + return self._connection.get_user(id) + + def get_emoji(self, id: int, /) -> Optional[Emoji]: + """Returns an emoji with the given ID. + + Parameters + ----------- + id: :class:`int` + The ID to search for. + + Returns + -------- + Optional[:class:`.Emoji`] + The custom emoji or ``None`` if not found. + """ + return self._connection.get_emoji(id) + + 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'. + + This is equivalent to: :: + + for guild in client.guilds: + for channel in guild.channels: + yield channel + + .. note:: + + Just because you receive a :class:`.abc.GuildChannel` does not mean that + you can communicate in said channel. :meth:`.abc.GuildChannel.permissions_for` should + be used for that. + + Yields + ------ + :class:`.abc.GuildChannel` + A channel the client can 'access'. + """ + + for guild in self.guilds: + yield from guild.channels + + def get_all_members(self) -> Generator[Member, None, None]: + """Returns a generator with every :class:`.Member` the client can see. + + This is equivalent to: :: + + for guild in client.guilds: + for member in guild.members: + yield member + + Yields + ------ + :class:`.Member` + A member the client can see. + """ + for guild in self.guilds: + yield from guild.members + + # listeners/waiters + + async def wait_until_ready(self) -> None: + """|coro| + + Waits until the client's internal cache is all ready. + """ + await self._ready.wait() + + def wait_for( + self, + event: str, + *, + check: Optional[Callable[..., bool]] = None, + timeout: Optional[float] = None, + ) -> Any: + """|coro| + + Waits for a WebSocket event to be dispatched. + + This could be used to wait for a user to reply to a message, + or to react to a message, or to edit a message in a self-contained + way. + + The ``timeout`` parameter is passed onto :func:`asyncio.wait_for`. By default, + it does not timeout. Note that this does propagate the + :exc:`asyncio.TimeoutError` for you in case of timeout and is provided for + ease of use. + + In case the event returns multiple arguments, a :class:`tuple` containing those + arguments is returned instead. Please check the + :ref:`documentation ` for a list of events and their + parameters. + + This function returns the **first event that meets the requirements**. + + Examples + --------- + + Waiting for a user reply: :: + + @client.event + async def on_message(message): + if message.content.startswith('$greet'): + channel = message.channel + await channel.send('Say hello!') + + def check(m): + return m.content == 'hello' and m.channel == channel + + msg = await client.wait_for('message', check=check) + await channel.send(f'Hello {msg.author}!') + + Waiting for a thumbs up reaction from the message author: :: + + @client.event + async def on_message(message): + if message.content.startswith('$thumb'): + channel = message.channel + await channel.send('Send me that \N{THUMBS UP SIGN} reaction, mate') + + def check(reaction, user): + return user == message.author and str(reaction.emoji) == '\N{THUMBS UP SIGN}' + + try: + reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check) + except asyncio.TimeoutError: + await channel.send('\N{THUMBS DOWN SIGN}') + else: + await channel.send('\N{THUMBS UP SIGN}') + + + Parameters + ------------ + event: :class:`str` + The event name, similar to the :ref:`event reference `, + but without the ``on_`` prefix, to wait for. + check: Optional[Callable[..., :class:`bool`]] + A predicate to check what to wait for. The arguments must meet the + parameters of the event being waited for. + timeout: Optional[:class:`float`] + The number of seconds to wait before timing out and raising + :exc:`asyncio.TimeoutError`. + + Raises + ------- + asyncio.TimeoutError + If a timeout is provided and it was reached. + + Returns + -------- + Any + Returns no arguments, a single argument, or a :class:`tuple` of multiple + arguments that mirrors the parameters passed in the + :ref:`event reference `. + """ + + future = self.loop.create_future() + if check is None: + def _check(*args): + return True + check = _check + + ev = event.lower() + try: + listeners = self._listeners[ev] + except KeyError: + listeners = [] + self._listeners[ev] = listeners + + listeners.append((future, check)) + return asyncio.wait_for(future, timeout) + + # event registration + + def event(self, coro: Coro) -> Coro: + """A decorator that registers an event to listen to. + + You can find more info about the events on the :ref:`documentation below `. + + The events must be a :ref:`coroutine `, if not, :exc:`TypeError` is raised. + + Example + --------- + + .. code-block:: python3 + + @client.event + async def on_ready(): + print('Ready!') + + Raises + -------- + TypeError + The coroutine passed is not actually a coroutine. + """ + + if not asyncio.iscoroutinefunction(coro): + raise TypeError('event registered must be a coroutine function') + + setattr(self, coro.__name__, coro) + _log.debug('%s has successfully been registered as an event', coro.__name__) + return coro + + async def change_presence( + self, + *, + activity: Optional[BaseActivity] = None, + status: Optional[Status] = None, + ): + """|coro| + + Changes the client's presence. + + Example + --------- + + .. code-block:: python3 + + game = discord.Game("with the API") + await client.change_presence(status=discord.Status.idle, activity=game) + + .. versionchanged:: 2.0 + Removed the ``afk`` keyword-only parameter. + + Parameters + ---------- + activity: Optional[:class:`.BaseActivity`] + The activity being done. ``None`` if no currently active activity is done. + status: Optional[:class:`.Status`] + Indicates what status to change to. If ``None``, then + :attr:`.Status.online` is used. + + Raises + ------ + :exc:`.InvalidArgument` + If the ``activity`` parameter is not the proper type. + """ + + if status is None: + status_str = 'online' + status = Status.online + elif status is Status.offline: + status_str = 'invisible' + status = Status.offline + else: + status_str = str(status) + + await self.ws.change_presence(activity=activity, status=status_str) + + for guild in self._connection.guilds: + me = guild.me + if me is None: + continue + + if activity is not None: + me.activities = (activity,) + else: + me.activities = () + + me.status = status + + # Guild stuff + + def fetch_guilds( + self, + *, + limit: Optional[int] = 100, + before: SnowflakeTime = None, + after: SnowflakeTime = None + ) -> GuildIterator: + """Retrieves an :class:`.AsyncIterator` that enables receiving your guilds. + + .. note:: + + Using this, you will only receive :attr:`.Guild.owner`, :attr:`.Guild.icon`, + :attr:`.Guild.id`, and :attr:`.Guild.name` per :class:`.Guild`. + + .. note:: + + This method is an API call. For general usage, consider :attr:`guilds` instead. + + Examples + --------- + + Usage :: + + async for guild in client.fetch_guilds(limit=150): + print(guild.name) + + Flattening into a list :: + + guilds = await client.fetch_guilds(limit=150).flatten() + # guilds is now a list of Guild... + + All parameters are optional. + + Parameters + ----------- + limit: Optional[:class:`int`] + The number of guilds to retrieve. + If ``None``, it retrieves every guild you have access to. Note, however, + that this would make it a slow operation. + Defaults to ``100``. + before: Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`] + Retrieves guilds before this date or object. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + after: Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`] + Retrieve guilds after this date or object. + If a datetime is provided, it is recommended to use a UTC aware datetime. + If the datetime is naive, it is assumed to be local time. + + Raises + ------ + :exc:`.HTTPException` + Getting the guilds failed. + + Yields + -------- + :class:`.Guild` + The guild with the guild data parsed. + """ + return GuildIterator(self, limit=limit, before=before, after=after) + + async def fetch_template(self, code: Union[Template, str]) -> Template: + """|coro| + + Gets a :class:`.Template` from a discord.new URL or code. + + Parameters + ----------- + code: Union[:class:`.Template`, :class:`str`] + The Discord Template Code or URL (must be a discord.new URL). + + Raises + ------- + :exc:`.NotFound` + The template is invalid. + :exc:`.HTTPException` + Getting the template failed. + + Returns + -------- + :class:`.Template` + The template from the URL/code. + """ + code = utils.resolve_template(code) + data = await self.http.get_template(code) + return Template(data=data, state=self._connection) # type: ignore + + async def fetch_guild(self, guild_id: int, /, *, with_counts=True) -> Guild: + """|coro| + + Retrieves a :class:`.Guild` from an ID. + + .. note:: + + Using this, you will **not** receive :attr:`.Guild.channels`, :attr:`.Guild.members`, + :attr:`.Member.activity` and :attr:`.Member.voice` per :class:`.Member`. + + .. note:: + + This method is an API call. For general usage, consider :meth:`get_guild` instead. + + Parameters + ----------- + guild_id: :class:`int` + The guild's ID to fetch from. + + with_counts: :class:`bool` + Whether to include count information in the guild. This fills the + :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count` + fields. + + .. versionadded:: 2.0 + Raises + ------ + :exc:`.Forbidden` + You do not have access to the guild. + :exc:`.HTTPException` + Getting the guild failed. + + Returns + -------- + :class:`.Guild` + The guild from the ID. + """ + data = await self.http.get_guild(guild_id, with_counts = with_counts) + return Guild(data=data, state=self._connection) + + async def create_guild( + self, + *, + name: str, + region: Union[VoiceRegion, str] = VoiceRegion.us_west, + icon: bytes = MISSING, + code: str = MISSING, + ) -> Guild: + """|coro| + + Creates a :class:`.Guild`. + + Bot accounts in more than 10 guilds are not allowed to create guilds. + + Parameters + ---------- + name: :class:`str` + The name of the guild. + region: :class:`.VoiceRegion` + The region for the voice communication server. + Defaults to :attr:`.VoiceRegion.us_west`. + icon: Optional[:class:`bytes`] + The :term:`py:bytes-like object` representing the icon. See :meth:`.ClientUser.edit` + for more details on what is expected. + code: :class:`str` + The code for a template to create the guild with. + + .. versionadded:: 1.4 + + Raises + ------ + :exc:`.HTTPException` + Guild creation failed. + :exc:`.InvalidArgument` + Invalid icon image format given. Must be PNG or JPG. + + Returns + ------- + :class:`.Guild` + The guild created. This is not the same guild that is + added to cache. + """ + if icon is not MISSING: + icon_base64 = utils._bytes_to_base64_data(icon) + else: + icon_base64 = None + + region_value = str(region) + + if code: + data = await self.http.create_from_template(code, name, region_value, icon_base64) + else: + data = await self.http.create_guild(name, region_value, icon_base64) + return Guild(data=data, state=self._connection) + + async def fetch_stage_instance(self, channel_id: int, /) -> StageInstance: + """|coro| + + Gets a :class:`.StageInstance` for a stage channel id. + + .. versionadded:: 2.0 + + Parameters + ----------- + channel_id: :class:`int` + The stage channel ID. + + Raises + ------- + :exc:`.NotFound` + The stage instance or channel could not be found. + :exc:`.HTTPException` + Getting the stage instance failed. + + Returns + -------- + :class:`.StageInstance` + The stage instance from the stage channel ID. + """ + data = await self.http.get_stage_instance(channel_id) + guild = self.get_guild(int(data['guild_id'])) + return StageInstance(guild=guild, state=self._connection, data=data) # type: ignore + + # Invite management + + async def fetch_invite(self, url: Union[Invite, str], *, with_counts: bool = True, with_expiration: bool = True) -> Invite: + """|coro| + + Gets an :class:`.Invite` from a discord.gg URL or ID. + + .. note:: + + If the invite is for a guild you have not joined, the guild and channel + attributes of the returned :class:`.Invite` will be :class:`.PartialInviteGuild` and + :class:`.PartialInviteChannel` respectively. + + Parameters + ----------- + url: Union[:class:`.Invite`, :class:`str`] + The Discord invite ID or URL (must be a discord.gg URL). + with_counts: :class:`bool` + Whether to include count information in the invite. This fills the + :attr:`.Invite.approximate_member_count` and :attr:`.Invite.approximate_presence_count` + fields. + with_expiration: :class:`bool` + Whether to include the expiration date of the invite. This fills the + :attr:`.Invite.expires_at` field. + + .. versionadded:: 2.0 + + Raises + ------- + :exc:`.NotFound` + The invite has expired or is invalid. + :exc:`.HTTPException` + Getting the invite failed. + + Returns + -------- + :class:`.Invite` + The invite from the URL/ID. + """ + + invite_id = utils.resolve_invite(url) + data = await self.http.get_invite(invite_id, with_counts=with_counts, with_expiration=with_expiration) + return Invite.from_incomplete(state=self._connection, data=data) + + async def delete_invite(self, invite: Union[Invite, str]) -> None: + """|coro| + + Revokes an :class:`.Invite`, URL, or ID to an invite. + + You must have the :attr:`~.Permissions.manage_channels` permission in + the associated guild to do this. + + Parameters + ---------- + invite: Union[:class:`.Invite`, :class:`str`] + The invite to revoke. + + Raises + ------- + :exc:`.Forbidden` + You do not have permissions to revoke invites. + :exc:`.NotFound` + The invite is invalid or expired. + :exc:`.HTTPException` + Revoking the invite failed. + """ + + invite_id = utils.resolve_invite(invite) + await self.http.delete_invite(invite_id) + + # Miscellaneous stuff + + async def fetch_widget(self, guild_id: int, /) -> Widget: + """|coro| + + Gets a :class:`.Widget` from a guild ID. + + .. note:: + + The guild must have the widget enabled to get this information. + + Parameters + ----------- + guild_id: :class:`int` + The ID of the guild. + + Raises + ------- + :exc:`.Forbidden` + The widget for this guild is disabled. + :exc:`.HTTPException` + Retrieving the widget failed. + + Returns + -------- + :class:`.Widget` + The guild's widget. + """ + data = await self.http.get_widget(guild_id) + + return Widget(state=self._connection, data=data) + + async def application_info(self) -> AppInfo: + """|coro| + + Retrieves the bot's application information. + + Raises + ------- + :exc:`.HTTPException` + Retrieving the information failed somehow. + + Returns + -------- + :class:`.AppInfo` + The bot's application information. + """ + data = await self.http.application_info() + if 'rpc_origins' not in data: + data['rpc_origins'] = None + return AppInfo(self._connection, data) + + async def fetch_user(self, user_id: int, /) -> User: + """|coro| + + Retrieves a :class:`~discord.User` based on their ID. + You do not have to share any guilds with the user to get this information, + however many operations do require that you do. + + .. note:: + + This method is an API call. If you have :attr:`discord.Intents.members` and member cache enabled, consider :meth:`get_user` instead. + + Parameters + ----------- + user_id: :class:`int` + The user's ID to fetch from. + + Raises + ------- + :exc:`.NotFound` + A user with this ID does not exist. + :exc:`.HTTPException` + Fetching the user failed. + + Returns + -------- + :class:`~discord.User` + The user you requested. + """ + data = await self.http.get_user(user_id) + return User(state=self._connection, data=data) + + async def fetch_channel(self, channel_id: int, /) -> Union[GuildChannel, PrivateChannel, Thread]: + """|coro| + + Retrieves a :class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, or :class:`.Thread` with the specified ID. + + .. note:: + + This method is an API call. For general usage, consider :meth:`get_channel` instead. + + .. versionadded:: 1.2 + + Raises + ------- + :exc:`.InvalidData` + An unknown channel type was received from Discord. + :exc:`.HTTPException` + Retrieving the channel failed. + :exc:`.NotFound` + Invalid Channel ID. + :exc:`.Forbidden` + You do not have permission to fetch this channel. + + Returns + -------- + Union[:class:`.abc.GuildChannel`, :class:`.abc.PrivateChannel`, :class:`.Thread`] + The channel from the ID. + """ + data = await self.http.get_channel(channel_id) + + factory, ch_type = _threaded_channel_factory(data['type']) + if factory is None: + raise InvalidData('Unknown channel type {type} for channel ID {id}.'.format_map(data)) + + if ch_type in (ChannelType.group, ChannelType.private): + # the factory will be a DMChannel or GroupChannel here + channel = factory(me=self.user, data=data, state=self._connection) # type: ignore + else: + # 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) + # GuildChannels expect a Guild, we may be passing an Object + channel = factory(guild=guild, state=self._connection, data=data) # type: ignore + + return channel + + async def fetch_webhook(self, webhook_id: int, /) -> Webhook: + """|coro| + + Retrieves a :class:`.Webhook` with the specified ID. + + Raises + -------- + :exc:`.HTTPException` + Retrieving the webhook failed. + :exc:`.NotFound` + Invalid webhook ID. + :exc:`.Forbidden` + You do not have permission to fetch this webhook. + + Returns + --------- + :class:`.Webhook` + The webhook you requested. + """ + data = await self.http.get_webhook(webhook_id) + return Webhook.from_state(data, state=self._connection) + + 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| + + Creates a :class:`.DMChannel` with this user. + + This should be rarely called, as this is done transparently for most + people. + + .. versionadded:: 2.0 + + Parameters + ----------- + user: :class:`~discord.abc.Snowflake` + The user to create a DM with. + + Returns + ------- + :class:`.DMChannel` + The channel that was created. + """ + state = self._connection + found = state._get_private_channel_by_user(user.id) + if found: + return found + + data = await state.http.start_private_message(user.id) + return state.add_dm_channel(data) + + def add_view(self, view: View, *, message_id: Optional[int] = None) -> None: + """Registers a :class:`~discord.ui.View` for persistent listening. + + This method should be used for when a view is comprised of components + that last longer than the lifecycle of the program. + + .. versionadded:: 2.0 + + Parameters + ------------ + view: :class:`discord.ui.View` + The view to register for dispatching. + message_id: Optional[:class:`int`] + The message ID that the view is attached to. This is currently used to + refresh the view's state during message update events. If not given + then message update events are not propagated for the view. + + Raises + ------- + TypeError + A view was not passed. + ValueError + The view is not persistent. A persistent view has no timeout + and all their components have an explicitly provided custom_id. + """ + + if not isinstance(view, View): + raise TypeError(f'expected an instance of View not {view.__class__!r}') + + if not view.is_persistent(): + raise ValueError('View is not persistent. Items need to have a custom_id set and View must have no timeout') + + self._connection.store_view(view, message_id) + + @property + def persistent_views(self) -> Sequence[View]: + """Sequence[:class:`.View`]: A sequence of persistent views added to the client. + + .. versionadded:: 2.0 + """ + return self._connection.persistent_views diff --git a/build/lib/discord/cog.py b/build/lib/discord/cog.py new file mode 100644 index 0000000000..6106579be9 --- /dev/null +++ b/build/lib/discord/cog.py @@ -0,0 +1,847 @@ +""" +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 + +import inspect +import importlib +import sys +import discord.utils +import types +from . import errors +from .commands import SlashCommand, UserCommand, MessageCommand, ApplicationCommand, SlashCommandGroup + +from typing import Any, Callable, Mapping, ClassVar, Dict, Generator, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Type + +from .commands.commands import _BaseCommand + +if TYPE_CHECKING: + from .commands import ApplicationContext, ApplicationCommand + +__all__ = ( + 'CogMeta', + 'Cog', + 'CogMixin' +) + +CogT = TypeVar('CogT', bound='Cog') +FuncT = TypeVar('FuncT', bound=Callable[..., Any]) + +MISSING: Any = discord.utils.MISSING + +def _is_submodule(parent: str, child: str) -> bool: + return parent == child or child.startswith(parent + ".") + +class CogMeta(type): + """A metaclass for defining a cog. + + Note that you should probably not use this directly. It is exposed + purely for documentation purposes along with making custom metaclasses to intermix + with other metaclasses such as the :class:`abc.ABCMeta` metaclass. + + For example, to create an abstract cog mixin class, the following would be done. + + .. code-block:: python3 + + import abc + + class CogABCMeta(commands.CogMeta, abc.ABCMeta): + pass + + class SomeMixin(metaclass=abc.ABCMeta): + pass + + class SomeCogMixin(SomeMixin, commands.Cog, metaclass=CogABCMeta): + pass + + .. note:: + + When passing an attribute of a metaclass that is documented below, note + that you must pass it as a keyword-only argument to the class creation + like the following example: + + .. code-block:: python3 + + class MyCog(commands.Cog, name='My Cog'): + pass + + Attributes + ----------- + name: :class:`str` + The cog name. By default, it is the name of the class with no modification. + description: :class:`str` + The cog description. By default, it is the cleaned docstring of the class. + + .. versionadded:: 1.6 + + command_attrs: :class:`dict` + A list of attributes to apply to every command inside this cog. The dictionary + is passed into the :class:`Command` options at ``__init__``. + If you specify attributes inside the command attribute in the class, it will + override the one specified inside this attribute. For example: + + .. code-block:: python3 + + class MyCog(commands.Cog, command_attrs=dict(hidden=True)): + @commands.command() + async def foo(self, ctx): + pass # hidden -> True + + @commands.command(hidden=False) + async def bar(self, ctx): + pass # hidden -> False + """ + __cog_name__: str + __cog_settings__: Dict[str, Any] + __cog_commands__: List[ApplicationCommand] + __cog_listeners__: List[Tuple[str, str]] + + def __new__(cls: Type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: + name, bases, attrs = args + attrs['__cog_name__'] = kwargs.pop('name', name) + attrs['__cog_settings__'] = kwargs.pop('command_attrs', {}) + + description = kwargs.pop('description', None) + if description is None: + description = inspect.cleandoc(attrs.get('__doc__', '')) + attrs['__cog_description__'] = description + + commands = {} + listeners = {} + no_bot_cog = 'Commands or listeners must not start with cog_ or bot_ (in method {0.__name__}.{1})' + + new_cls = super().__new__(cls, name, bases, attrs, **kwargs) + + valid_commands = [(c for i, c in j.__dict__.items() if isinstance(c, _BaseCommand)) for j in reversed(new_cls.__mro__)] + if any(isinstance(i, ApplicationCommand) for i in valid_commands) and any(not isinstance(i, _BaseCommand) for i in valid_commands): + _filter = ApplicationCommand + else: + _filter = _BaseCommand + + for base in reversed(new_cls.__mro__): + for elem, value in base.__dict__.items(): + if elem in commands: + del commands[elem] + if elem in listeners: + del listeners[elem] + + try: + if getattr(value, "parent") is not None and isinstance(value, ApplicationCommand): + # Skip commands if they are a part of a group + continue + except AttributeError: + pass + + is_static_method = isinstance(value, staticmethod) + if is_static_method: + value = value.__func__ + if isinstance(value, _filter): + if is_static_method: + raise TypeError(f'Command in method {base}.{elem!r} must not be staticmethod.') + if elem.startswith(('cog_', 'bot_')): + raise TypeError(no_bot_cog.format(base, elem)) + commands[elem] = value + elif inspect.iscoroutinefunction(value): + try: + getattr(value, '__cog_listener__') + except AttributeError: + continue + else: + if elem.startswith(('cog_', 'bot_')): + raise TypeError(no_bot_cog.format(base, elem)) + listeners[elem] = value + + new_cls.__cog_commands__ = list(commands.values()) + + listeners_as_list = [] + for listener in listeners.values(): + for listener_name in listener.__cog_listener_names__: + # I use __name__ instead of just storing the value so I can inject + # the self attribute when the time comes to add them to the bot + listeners_as_list.append((listener_name, listener.__name__)) + + new_cls.__cog_listeners__ = listeners_as_list + + cmd_attrs = new_cls.__cog_settings__ + + # Either update the command with the cog provided defaults or copy it. + # r.e type ignore, type-checker complains about overriding a ClassVar + new_cls.__cog_commands__ = tuple(c._update_copy(cmd_attrs) for c in new_cls.__cog_commands__) # type: ignore + + lookup = { + cmd.qualified_name: cmd + for cmd in new_cls.__cog_commands__ + } + + # Update the Command instances dynamically as well + for command in new_cls.__cog_commands__: + if not isinstance(command, ApplicationCommand): + setattr(new_cls, command.callback.__name__, command) + parent = command.parent + if parent is not None: + # Get the latest parent reference + parent = lookup[parent.qualified_name] # type: ignore + + # Update our parent's reference to our self + parent.remove_command(command.name) # type: ignore + parent.add_command(command) # type: ignore + + return new_cls + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args) + + @classmethod + def qualified_name(cls) -> str: + return cls.__cog_name__ + +def _cog_special_method(func: FuncT) -> FuncT: + func.__cog_special_method__ = None + return func + +class Cog(metaclass=CogMeta): + """The base class that all cogs must inherit from. + + A cog is a collection of commands, listeners, and optional state to + help group commands together. More information on them can be found on + the :ref:`ext_commands_cogs` page. + + When inheriting from this class, the options shown in :class:`CogMeta` + are equally valid here. + """ + __cog_name__: ClassVar[str] + __cog_settings__: ClassVar[Dict[str, Any]] + __cog_commands__: ClassVar[List[ApplicationCommand]] + __cog_listeners__: ClassVar[List[Tuple[str, str]]] + + def __new__(cls: Type[CogT], *args: Any, **kwargs: Any) -> CogT: + # For issue 426, we need to store a copy of the command objects + # since we modify them to inject `self` to them. + # To do this, we need to interfere with the Cog creation process. + self = super().__new__(cls) + + return self + + def get_commands(self) -> List[ApplicationCommand]: + r""" + Returns + -------- + List[:class:`.ApplicationCommand`] + A :class:`list` of :class:`.ApplicationCommand`\s that are + defined inside this cog. + + .. note:: + + This does not include subcommands. + """ + return [ + c for c in ( + c for c in self.__cog_commands__ + if not isinstance(c, (SlashCommand, MessageCommand, UserCommand)) + ) if c.parent is None + ] + [ + c for c in self.__cog_commands__ + if isinstance(c, (SlashCommand, MessageCommand, UserCommand)) + ] + + @property + def qualified_name(self) -> str: + """:class:`str`: Returns the cog's specified name, not the class name.""" + return self.__cog_name__ + + @property + def description(self) -> str: + """:class:`str`: Returns the cog's description, typically the cleaned docstring.""" + return self.__cog_description__ + + @description.setter + def description(self, description: str) -> None: + self.__cog_description__ = description + + def walk_commands(self) -> Generator[ApplicationCommand, None, None]: + """An iterator that recursively walks through this cog's commands and subcommands. + + Yields + ------ + Union[:class:`.Command`, :class:`.Group`] + A command or group from the cog. + """ + for command in self.__cog_commands__: + if command.parent is None: + yield command + + 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 + -------- + List[Tuple[:class:`str`, :ref:`coroutine `]] + The listeners defined in this cog. + """ + return [(name, getattr(self, method_name)) for name, method_name in self.__cog_listeners__] + + @classmethod + def _get_overridden_method(cls, method: FuncT) -> Optional[FuncT]: + """Return None if the method is not overridden. Otherwise returns the overridden method.""" + return getattr(getattr(method, "__func__", method), '__cog_special_method__', method) + + @classmethod + def listener(cls, name: str = MISSING) -> Callable[[FuncT], FuncT]: + """A decorator that marks a function as a listener. + + This is the cog equivalent of :meth:`.Bot.listen`. + + Parameters + ------------ + name: :class:`str` + The name of the event being listened to. If not provided, it + defaults to the function's name. + + Raises + -------- + TypeError + The function is not a coroutine function or a string was not passed as + the name. + """ + + if name is not MISSING and not isinstance(name, str): + raise TypeError(f'Cog.listener expected str but received {name.__class__.__name__!r} instead.') + + def decorator(func: FuncT) -> FuncT: + actual = func + if isinstance(actual, staticmethod): + actual = actual.__func__ + if not inspect.iscoroutinefunction(actual): + raise TypeError('Listener function must be a coroutine function.') + actual.__cog_listener__ = True + to_assign = name or actual.__name__ + try: + actual.__cog_listener_names__.append(to_assign) + except AttributeError: + actual.__cog_listener_names__ = [to_assign] + # we have to return `func` instead of `actual` because + # we need the type to be `staticmethod` for the metaclass + # to pick it up but the metaclass unfurls the function and + # thus the assignments need to be on the actual function + return func + return decorator + + def has_error_handler(self) -> bool: + """:class:`bool`: Checks whether the cog has an error handler. + + .. versionadded:: 1.7 + """ + return not hasattr(self.cog_command_error.__func__, '__cog_special_method__') + + @_cog_special_method + def cog_unload(self) -> None: + """A special method that is called when the cog gets removed. + + This function **cannot** be a coroutine. It must be a regular + function. + + Subclasses must replace this if they want special unloading behaviour. + """ + pass + + @_cog_special_method + def bot_check_once(self, ctx: ApplicationContext) -> bool: + """A special method that registers as a :meth:`.Bot.check_once` + check. + + This function **can** be a coroutine and must take a sole parameter, + ``ctx``, to represent the :class:`.Context`. + """ + return True + + @_cog_special_method + def bot_check(self, ctx: ApplicationContext) -> bool: + """A special method that registers as a :meth:`.Bot.check` + check. + + This function **can** be a coroutine and must take a sole parameter, + ``ctx``, to represent the :class:`.Context`. + """ + return True + + @_cog_special_method + def cog_check(self, ctx: ApplicationContext) -> bool: + """A special method that registers as a :func:`~discord.ext.commands.check` + for every command and subcommand in this cog. + + This function **can** be a coroutine and must take a sole parameter, + ``ctx``, to represent the :class:`.Context`. + """ + return True + + @_cog_special_method + async def cog_command_error(self, ctx: ApplicationContext, error: Exception) -> None: + """A special method that is called whenever an error + is dispatched inside this cog. + + This is similar to :func:`.on_command_error` except only applying + to the commands inside this cog. + + This **must** be a coroutine. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context where the error happened. + error: :class:`CommandError` + The error that happened. + """ + pass + + @_cog_special_method + async def cog_before_invoke(self, ctx: ApplicationContext) -> None: + """A special method that acts as a cog local pre-invoke hook. + + This is similar to :meth:`.Command.before_invoke`. + + This **must** be a coroutine. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context. + """ + pass + + @_cog_special_method + async def cog_after_invoke(self, ctx: ApplicationContext) -> None: + """A special method that acts as a cog local post-invoke hook. + + This is similar to :meth:`.Command.after_invoke`. + + This **must** be a coroutine. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context. + """ + pass + + def _inject(self: CogT, bot) -> CogT: + cls = self.__class__ + + # realistically, the only thing that can cause loading errors + # is essentially just the command loading, which raises if there are + # duplicates. When this condition is met, we want to undo all what + # we've added so far for some form of atomic loading. + + for index, command in enumerate(self.__cog_commands__): + command._set_cog(self) + + if not isinstance(command, ApplicationCommand): + if command.parent is None: + try: + bot.add_command(command) + except Exception as e: + # undo our additions + for to_undo in self.__cog_commands__[:index]: + if to_undo.parent is None: + bot.remove_command(to_undo.name) + raise e + else: + bot.add_application_command(command) + + # check if we're overriding the default + if cls.bot_check is not Cog.bot_check: + bot.add_check(self.bot_check) + + if cls.bot_check_once is not Cog.bot_check_once: + bot.add_check(self.bot_check_once, call_once=True) + + # while Bot.add_listener can raise if it's not a coroutine, + # this precondition is already met by the listener decorator + # already, thus this should never raise. + # Outside of, memory errors and the like... + for name, method_name in self.__cog_listeners__: + bot.add_listener(getattr(self, method_name), name) + + return self + + def _eject(self, bot) -> None: + cls = self.__class__ + + try: + for command in self.__cog_commands__: + if isinstance(command, ApplicationCommand): + bot.remove_application_command(command) + else: + if command.parent is None: + bot.remove_command(command.name) + + for _, method_name in self.__cog_listeners__: + bot.remove_listener(getattr(self, method_name)) + + if cls.bot_check is not Cog.bot_check: + bot.remove_check(self.bot_check) + + if cls.bot_check_once is not Cog.bot_check_once: + bot.remove_check(self.bot_check_once, call_once=True) + finally: + try: + self.cog_unload() + except Exception: + pass + +class CogMixin: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__cogs: Dict[str, Cog] = {} + self.__extensions: Dict[str, types.ModuleType] = {} + + def add_cog(self, cog: Cog, *, override: bool = False) -> None: + """Adds a "cog" to the bot. + + A cog is a class that has its own event listeners and commands. + + .. versionchanged:: 2.0 + + :exc:`.ClientException` is raised when a cog with the same name + is already loaded. + + Parameters + ----------- + cog: :class:`.Cog` + The cog to register to the bot. + override: :class:`bool` + If a previously loaded cog with the same name should be ejected + instead of raising an error. + + .. versionadded:: 2.0 + + Raises + ------- + TypeError + The cog does not inherit from :class:`.Cog`. + CommandError + An error happened during loading. + .ClientException + A cog with the same name is already loaded. + """ + + if not isinstance(cog, Cog): + raise TypeError('cogs must derive from Cog') + + cog_name = cog.__cog_name__ + existing = self.__cogs.get(cog_name) + + if existing is not None: + if not override: + raise discord.ClientException(f'Cog named {cog_name!r} already loaded') + self.remove_cog(cog_name) + + cog = cog._inject(self) + self.__cogs[cog_name] = cog + + def get_cog(self, name: str) -> Optional[Cog]: + """Gets the cog instance requested. + + If the cog is not found, ``None`` is returned instead. + + Parameters + ----------- + name: :class:`str` + The name of the cog you are requesting. + This is equivalent to the name passed via keyword + argument in class creation or the class name if unspecified. + + Returns + -------- + Optional[:class:`Cog`] + The cog that was requested. If not found, returns ``None``. + """ + return self.__cogs.get(name) + + def remove_cog(self, name: str) -> Optional[Cog]: + """Removes a cog from the bot and returns it. + + All registered commands and event listeners that the + cog has registered will be removed as well. + + If no cog is found then this method has no effect. + + Parameters + ----------- + name: :class:`str` + 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) + if cog is None: + return + + if hasattr(self, "_help_command"): + help_command = self._help_command + if help_command and help_command.cog is cog: + help_command.cog = None + + cog._eject(self) + + return cog + + @property + def cogs(self) -> Mapping[str, Cog]: + """Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog.""" + return types.MappingProxyType(self.__cogs) + + # extensions + + def _remove_module_references(self, name: str) -> None: + # find all references to the module + # remove the cogs registered from the module + for cogname, cog in self.__cogs.copy().items(): + if _is_submodule(name, cog.__module__): + self.remove_cog(cogname) + + # remove all the commands from the module + for cmd in self.all_commands.copy().values(): + if cmd.module is not None and _is_submodule(name, cmd.module): + # if isinstance(cmd, GroupMixin): + # cmd.recursively_remove_all_commands() + self.remove_command(cmd.name) + + # remove all the listeners from the module + for event_list in self.extra_events.copy().values(): + remove = [] + for index, event in enumerate(event_list): + if event.__module__ is not None and _is_submodule(name, event.__module__): + remove.append(index) + + for index in reversed(remove): + del event_list[index] + + def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None: + try: + func = getattr(lib, 'teardown') + except AttributeError: + pass + else: + try: + func(self) + except Exception: + pass + finally: + self.__extensions.pop(key, None) + sys.modules.pop(key, None) + name = lib.__name__ + for module in list(sys.modules.keys()): + if _is_submodule(name, module): + del sys.modules[module] + + def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None: + # precondition: key not in self.__extensions + lib = importlib.util.module_from_spec(spec) + sys.modules[key] = lib + try: + spec.loader.exec_module(lib) # type: ignore + except Exception as e: + del sys.modules[key] + raise errors.ExtensionFailed(key, e) from e + + try: + setup = getattr(lib, 'setup') + except AttributeError: + del sys.modules[key] + raise errors.NoEntryPointError(key) + + try: + setup(self) + except Exception as e: + del sys.modules[key] + self._remove_module_references(lib.__name__) + self._call_module_finalizers(lib, key) + raise errors.ExtensionFailed(key, e) from e + else: + self.__extensions[key] = lib + + def _resolve_name(self, name: str, package: Optional[str]) -> str: + try: + return importlib.util.resolve_name(name, package) + except ImportError: + raise errors.ExtensionNotFound(name) + + def load_extension(self, name: str, *, package: Optional[str] = None) -> None: + """Loads an extension. + + An extension is a python module that contains commands, cogs, or + listeners. + + An extension must have a global function, ``setup`` defined as + the entry point on what to do when the extension is loaded. This entry + point must have a single argument, the ``bot``. + + Parameters + ------------ + name: :class:`str` + The extension name to load. It must be dot separated like + regular Python imports if accessing a sub-module. e.g. + ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when loading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 + + Raises + -------- + ExtensionNotFound + The extension could not be imported. + This is also raised if the name of the extension could not + be resolved using the provided ``package`` parameter. + ExtensionAlreadyLoaded + The extension is already loaded. + NoEntryPointError + The extension does not have a setup function. + ExtensionFailed + The extension or its setup function had an execution error. + """ + + name = self._resolve_name(name, package) + if name in self.__extensions: + raise errors.ExtensionAlreadyLoaded(name) + + spec = importlib.util.find_spec(name) + if spec is None: + raise errors.ExtensionNotFound(name) + + self._load_from_module_spec(spec, name) + + def unload_extension(self, name: str, *, package: Optional[str] = None) -> None: + """Unloads an extension. + + When the extension is unloaded, all commands, listeners, and cogs are + removed from the bot and the module is un-imported. + + The extension can provide an optional global function, ``teardown``, + to do miscellaneous clean-up if necessary. This function takes a single + parameter, the ``bot``, similar to ``setup`` from + :meth:`~.Bot.load_extension`. + + Parameters + ------------ + name: :class:`str` + The extension name to unload. It must be dot separated like + regular Python imports if accessing a sub-module. e.g. + ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when unloading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 + + Raises + ------- + ExtensionNotFound + The name of the extension could not + be resolved using the provided ``package`` parameter. + ExtensionNotLoaded + The extension was not loaded. + """ + + name = self._resolve_name(name, package) + lib = self.__extensions.get(name) + if lib is None: + raise errors.ExtensionNotLoaded(name) + + self._remove_module_references(lib.__name__) + self._call_module_finalizers(lib, name) + + def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: + """Atomically reloads an extension. + + This replaces the extension with the same extension, only refreshed. This is + equivalent to a :meth:`unload_extension` followed by a :meth:`load_extension` + except done in an atomic way. That is, if an operation fails mid-reload then + the bot will roll-back to the prior working state. + + Parameters + ------------ + name: :class:`str` + The extension name to reload. It must be dot separated like + regular Python imports if accessing a sub-module. e.g. + ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when reloading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 + + Raises + ------- + ExtensionNotLoaded + The extension was not loaded. + ExtensionNotFound + The extension could not be imported. + This is also raised if the name of the extension could not + be resolved using the provided ``package`` parameter. + NoEntryPointError + The extension does not have a setup function. + ExtensionFailed + The extension setup function had an execution error. + """ + + name = self._resolve_name(name, package) + lib = self.__extensions.get(name) + if lib is None: + raise errors.ExtensionNotLoaded(name) + + # get the previous module states from sys modules + modules = { + name: module + for name, module in sys.modules.items() + if _is_submodule(lib.__name__, name) + } + + try: + # Unload and then load the module... + self._remove_module_references(lib.__name__) + self._call_module_finalizers(lib, name) + self.load_extension(name) + except Exception: + # if the load failed, the remnants should have been + # cleaned from the load_extension function call + # so let's load it from our old compiled library. + lib.setup(self) # type: ignore + self.__extensions[name] = lib + + # revert sys.modules back to normal and raise back to caller + sys.modules.update(modules) + raise + + @property + def extensions(self) -> Mapping[str, types.ModuleType]: + """Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension.""" + return types.MappingProxyType(self.__extensions) diff --git a/build/lib/discord/colour.py b/build/lib/discord/colour.py new file mode 100644 index 0000000000..1f300019c6 --- /dev/null +++ b/build/lib/discord/colour.py @@ -0,0 +1,363 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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. +""" + +import colorsys +import random + +from typing import ( + Any, + Optional, + Tuple, + Type, + TypeVar, + Union, +) + +__all__ = ( + 'Colour', + 'Color', +) + +CT = TypeVar('CT', bound='Colour') + + +class Colour: + """Represents a Discord role colour. This class is similar + to a (red, green, blue) :class:`tuple`. + + There is an alias for this called Color. + + .. container:: operations + + .. describe:: x == y + + Checks if two colours are equal. + + .. describe:: x != y + + Checks if two colours are not equal. + + .. describe:: hash(x) + + Return the colour's hash. + + .. describe:: str(x) + + Returns the hex format for the colour. + + .. describe:: int(x) + + Returns the raw colour value. + + Attributes + ------------ + value: :class:`int` + The raw integer colour value. + """ + + __slots__ = ('value',) + + def __init__(self, value: int): + if not isinstance(value, int): + raise TypeError(f'Expected int parameter, received {value.__class__.__name__} instead.') + + self.value: int = value + + def _get_byte(self, byte: int) -> int: + return (self.value >> (8 * byte)) & 0xff + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Colour) and self.value == other.value + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __str__(self) -> str: + return f'#{self.value:0>6x}' + + def __int__(self) -> int: + return self.value + + def __repr__(self) -> str: + return f'' + + def __hash__(self) -> int: + return hash(self.value) + + @property + def r(self) -> int: + """:class:`int`: Returns the red component of the colour.""" + return self._get_byte(2) + + @property + def g(self) -> int: + """:class:`int`: Returns the green component of the colour.""" + return self._get_byte(1) + + @property + def b(self) -> int: + """:class:`int`: Returns the blue component of the colour.""" + return self._get_byte(0) + + def to_rgb(self) -> Tuple[int, int, int]: + """Tuple[:class:`int`, :class:`int`, :class:`int`]: Returns an (r, g, b) tuple representing the colour.""" + return (self.r, self.g, self.b) + + @classmethod + def from_rgb(cls: Type[CT], r: int, g: int, b: int) -> CT: + """Constructs a :class:`Colour` from an RGB tuple.""" + return cls((r << 16) + (g << 8) + b) + + @classmethod + def from_hsv(cls: Type[CT], h: float, s: float, v: float) -> CT: + """Constructs a :class:`Colour` from an HSV tuple.""" + rgb = colorsys.hsv_to_rgb(h, s, v) + return cls.from_rgb(*(int(x * 255) for x in rgb)) + + @classmethod + def default(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0``.""" + return cls(0) + + @classmethod + def random(cls: Type[CT], *, seed: Optional[Union[int, str, float, bytes, bytearray]] = None) -> CT: + """A factory method that returns a :class:`Colour` with a random hue. + + .. note:: + + The random algorithm works by choosing a colour with a random hue but + with maxed out saturation and value. + + .. versionadded:: 1.6 + + Parameters + ------------ + seed: Optional[Union[:class:`int`, :class:`str`, :class:`float`, :class:`bytes`, :class:`bytearray`]] + The seed to initialize the RNG with. If ``None`` is passed the default RNG is used. + + .. versionadded:: 1.7 + """ + rand = random if seed is None else random.Random(seed) + return cls.from_hsv(rand.random(), 1, 1) + + @classmethod + def teal(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x1abc9c``.""" + return cls(0x1abc9c) + + @classmethod + def dark_teal(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``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 + def green(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x2ecc71``.""" + return cls(0x2ecc71) + + @classmethod + def dark_green(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x1f8b4c``.""" + return cls(0x1f8b4c) + + @classmethod + def blue(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x3498db``.""" + return cls(0x3498db) + + @classmethod + def dark_blue(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x206694``.""" + return cls(0x206694) + + @classmethod + def purple(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x9b59b6``.""" + return cls(0x9b59b6) + + @classmethod + def dark_purple(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x71368a``.""" + return cls(0x71368a) + + @classmethod + def magenta(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xe91e63``.""" + return cls(0xe91e63) + + @classmethod + def dark_magenta(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xad1457``.""" + return cls(0xad1457) + + @classmethod + def gold(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xf1c40f``.""" + return cls(0xf1c40f) + + @classmethod + def dark_gold(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xc27c0e``.""" + return cls(0xc27c0e) + + @classmethod + def orange(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xe67e22``.""" + return cls(0xe67e22) + + @classmethod + def dark_orange(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``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 + def red(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xe74c3c``.""" + return cls(0xe74c3c) + + @classmethod + def dark_red(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x992d22``.""" + return cls(0x992d22) + + @classmethod + def lighter_grey(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x95a5a6``.""" + return cls(0x95a5a6) + + lighter_gray = lighter_grey + + @classmethod + def dark_grey(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x607d8b``.""" + return cls(0x607d8b) + + dark_gray = dark_grey + + @classmethod + def light_grey(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x979c9f``.""" + return cls(0x979c9f) + + light_gray = light_grey + + @classmethod + def darker_grey(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x546e7a``.""" + return cls(0x546e7a) + + darker_gray = darker_grey + + @classmethod + def og_blurple(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x7289da``.""" + return cls(0x7289da) + + @classmethod + def blurple(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x5865F2``.""" + return cls(0x5865F2) + + @classmethod + def greyple(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x99aab5``.""" + return cls(0x99aab5) + + @classmethod + def dark_theme(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0x36393F``. + This will appear transparent on Discord's dark theme. + + .. versionadded:: 1.5 + """ + return cls(0x36393F) + + @classmethod + def fuchsia(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xEB459E``. + + .. versionadded:: 2.0 + """ + return cls(0xEB459E) + + @classmethod + def yellow(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xFEE75C``. + + .. versionadded:: 2.0 + """ + return cls(0xFEE75C) + + @classmethod + def nitro_pink(cls: Type[CT]) -> CT: + """A factory method that returns a :class:`Colour` with a value of ``0xf47fff``. + + .. versionadded:: 2.0 + """ + return cls(0xf47fff) + + @classmethod + def embed_background(cls: Type[CT], theme: str = "dark") -> CT: + """A factory method that returns a :class:`Color` corresponding to the embed colors on discord clients, with a value of + ``0x2F3136`` (dark) + ``0xf2f3f5`` (light) + ``0x000000`` (amoled). + + .. versionadded:: 2.0 + + Parameters + ----------- + theme: :class:`str` + The theme color to apply, must be one of "dark", "light", or "amoled". + """ + themes_cls = { + "dark": 0x2F3136, + "light": 0xf2f3f5, + "amoled": 0x000000, + } + + if theme not in themes_cls: + raise TypeError("Theme must be \"dark\", \"light\", or \"amoled\".") + + return cls(themes_cls[theme]) + + +Color = Colour diff --git a/build/lib/discord/commands/__init__.py b/build/lib/discord/commands/__init__.py new file mode 100644 index 0000000000..6662813329 --- /dev/null +++ b/build/lib/discord/commands/__init__.py @@ -0,0 +1,29 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 .commands import * +from .context import * +from .errors import * +from .permissions import * diff --git a/build/lib/discord/commands/commands.py b/build/lib/discord/commands/commands.py new file mode 100644 index 0000000000..bb415d440f --- /dev/null +++ b/build/lib/discord/commands/commands.py @@ -0,0 +1,1330 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import asyncio +import functools +import inspect +import re +import types +from collections import OrderedDict +from typing import Any, Callable, Dict, List, Optional, Type, Union, TYPE_CHECKING + +from .context import ApplicationContext, AutocompleteContext +from .errors import ApplicationCommandError, CheckFailure, ApplicationCommandInvokeError +from .permissions import Permission +from ..enums import SlashCommandOptionType, ChannelType +from ..errors import ValidationError, ClientException +from ..member import Member +from ..message import Message +from ..user import User +from ..utils import find, get_or_fetch, async_all + +__all__ = ( + "_BaseCommand", + "ApplicationCommand", + "SlashCommand", + "Option", + "OptionChoice", + "option", + "slash_command", + "application_command", + "user_command", + "message_command", + "command", + "SlashCommandGroup", + "ContextMenuCommand", + "UserCommand", + "MessageCommand", +) + +if TYPE_CHECKING: + from ..interactions import Interaction + +def wrap_callback(coro): + @functools.wraps(coro) + async def wrapped(*args, **kwargs): + try: + ret = await coro(*args, **kwargs) + except ApplicationCommandError: + raise + except asyncio.CancelledError: + return + except Exception as exc: + raise ApplicationCommandInvokeError(exc) from exc + return ret + return wrapped + +def hooked_wrapped_callback(command, ctx, coro): + @functools.wraps(coro) + async def wrapped(arg): + try: + ret = await coro(arg) + except ApplicationCommandError: + raise + except asyncio.CancelledError: + return + except Exception as exc: + raise ApplicationCommandInvokeError(exc) from exc + finally: + await command.call_after_hooks(ctx) + return ret + return wrapped + +class _BaseCommand: + __slots__ = () + +class ApplicationCommand(_BaseCommand): + cog = None + + def __repr__(self): + return f"" + + def __eq__(self, other): + return isinstance(other, self.__class__) + + async def __call__(self, ctx, *args, **kwargs): + """|coro| + Calls the command's callback. + + This method bypasses all checks that a command has and does not + convert the arguments beforehand, so take care to pass the correct + arguments in. + """ + return await self.callback(ctx, *args, **kwargs) + + async def prepare(self, ctx: ApplicationContext) -> None: + # This should be same across all 3 types + ctx.command = self + + if not await self.can_run(ctx): + raise CheckFailure(f'The check functions for the command {self.name} failed') + + # TODO: Add cooldown + + await self.call_before_hooks(ctx) + + async def invoke(self, ctx: ApplicationContext) -> None: + await self.prepare(ctx) + + injected = hooked_wrapped_callback(self, ctx, self._invoke) + await injected(ctx) + + async def can_run(self, ctx: ApplicationContext) -> bool: + + if not await ctx.bot.can_run(ctx): + raise CheckFailure(f'The global check functions for command {self.name} failed.') + + predicates = self.checks + if not predicates: + # since we have no checks, then we just return True. + return True + + return await async_all(predicate(ctx) for predicate in predicates) # type: ignore + + async def dispatch_error(self, ctx: ApplicationContext, error: Exception) -> None: + ctx.command_failed = True + cog = self.cog + try: + coro = self.on_error + except AttributeError: + pass + else: + injected = wrap_callback(coro) + if cog is not None: + await injected(cog, ctx, error) + else: + await injected(ctx, error) + + try: + if cog is not None: + local = cog.__class__._get_overridden_method(cog.cog_command_error) + if local is not None: + wrapped = wrap_callback(local) + await wrapped(ctx, error) + finally: + ctx.bot.dispatch('application_command_error', ctx, error) + + def _get_signature_parameters(self): + return OrderedDict(inspect.signature(self.callback).parameters) + + def error(self, coro): + """A decorator that registers a coroutine as a local error handler. + + A local error handler is an :func:`.on_command_error` event limited to + a single command. However, the :func:`.on_command_error` is still + invoked afterwards as the catch-all. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the local error handler. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The error handler must be a coroutine.') + + self.on_error = coro + return coro + + def has_error_handler(self) -> bool: + """:class:`bool`: Checks whether the command has an error handler registered. + """ + return hasattr(self, 'on_error') + + def before_invoke(self, coro): + """A decorator that registers a coroutine as a pre-invoke hook. + A pre-invoke hook is called directly before the command is + called. This makes it a useful function to set up database + connections or any type of set up required. + This pre-invoke hook takes a sole parameter, a :class:`.Context`. + See :meth:`.Bot.before_invoke` for more info. + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the pre-invoke hook. + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The pre-invoke hook must be a coroutine.') + + self._before_invoke = coro + return coro + + def after_invoke(self, coro): + """A decorator that registers a coroutine as a post-invoke hook. + A post-invoke hook is called directly after the command is + called. This makes it a useful function to clean-up database + connections or any type of clean up required. + This post-invoke hook takes a sole parameter, a :class:`.Context`. + See :meth:`.Bot.after_invoke` for more info. + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the post-invoke hook. + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The post-invoke hook must be a coroutine.') + + self._after_invoke = coro + return coro + + async def call_before_hooks(self, ctx: ApplicationContext) -> None: + # now that we're done preparing we can call the pre-command hooks + # first, call the command local hook: + cog = self.cog + if self._before_invoke is not None: + # should be cog if @commands.before_invoke is used + instance = getattr(self._before_invoke, '__self__', cog) + # __self__ only exists for methods, not functions + # however, if @command.before_invoke is used, it will be a function + if instance: + await self._before_invoke(instance, ctx) # type: ignore + else: + await self._before_invoke(ctx) # type: ignore + + # call the cog local hook if applicable: + if cog is not None: + hook = cog.__class__._get_overridden_method(cog.cog_before_invoke) + if hook is not None: + await hook(ctx) + + # call the bot global hook if necessary + hook = ctx.bot._before_invoke + if hook is not None: + await hook(ctx) + + async def call_after_hooks(self, ctx: ApplicationContext) -> None: + cog = self.cog + if self._after_invoke is not None: + instance = getattr(self._after_invoke, '__self__', cog) + if instance: + await self._after_invoke(instance, ctx) # type: ignore + else: + await self._after_invoke(ctx) # type: ignore + + # call the cog local hook if applicable: + if cog is not None: + hook = cog.__class__._get_overridden_method(cog.cog_after_invoke) + if hook is not None: + await hook(ctx) + + hook = ctx.bot._after_invoke + if hook is not None: + await hook(ctx) + + @property + def full_parent_name(self) -> str: + """:class:`str`: Retrieves the fully qualified parent command name. + + This the base command name required to execute it. For example, + in ``/one two three`` the parent name would be ``one two``. + """ + entries = [] + command = self + while command.parent is not None and hasattr(command.parent, "name"): + command = command.parent + entries.append(command.name) + + return ' '.join(reversed(entries)) + + def qualified_name(self) -> str: + """:class:`str`: Retrieves the fully qualified command name. + + This is the full parent name with the command name as well. + For example, in ``/one two three`` the qualified name would be + ``one two three``. + """ + + parent = self.full_parent_name + + if parent: + return parent + ' ' + self.name + else: + return self.name + + def _set_cog(self, cog): + self.cog = cog + +class SlashCommand(ApplicationCommand): + r"""A class that implements the protocol for a slash command. + + These are not created manually, instead they are created via the + decorator or functional interface. + + Attributes + ----------- + name: :class:`str` + The name of the command. + callback: :ref:`coroutine ` + The coroutine that is executed when the command is called. + description: Optional[:class:`str`] + The description for the command. + guild_ids: Optional[List[:class:`int`]] + The ids of the guilds where this command will be registered. + options: List[:class:`Option`] + The parameters for this command. + parent: Optional[:class:`SlashCommandGroup`] + The parent group that this command belongs to. ``None`` if there + isn't one. + default_permission: :class:`bool` + Whether the command is enabled by default when it is added to a guild. + permissions: List[:class:`Permission`] + The permissions for this command. + + .. note:: + + If this is not empty then default_permissions will be set to False. + + cog: Optional[:class:`Cog`] + The cog that this command belongs to. ``None`` if there isn't one. + checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]] + A list of predicates that verifies if the command could be executed + with the given :class:`.ApplicationContext` as the sole parameter. If an exception + is necessary to be thrown to signal failure, then one inherited from + :exc:`.CommandError` should be used. Note that if the checks fail then + :exc:`.CheckFailure` exception is raised to the :func:`.on_application_command_error` + event. + """ + type = 1 + + def __new__(cls, *args, **kwargs) -> SlashCommand: + self = super().__new__(cls) + + self.__original_kwargs__ = kwargs.copy() + return self + + def __init__(self, func: Callable, *args, **kwargs) -> None: + if not asyncio.iscoroutinefunction(func): + raise TypeError("Callback must be a coroutine.") + self.callback = func + + self.guild_ids: Optional[List[int]] = kwargs.get("guild_ids", None) + + name = kwargs.get("name") or func.__name__ + validate_chat_input_name(name) + self.name: str = name + self.id = None + + description = kwargs.get("description") or ( + inspect.cleandoc(func.__doc__).splitlines()[0] + if func.__doc__ is not None + else "No description provided" + ) + validate_chat_input_description(description) + self.description: str = description + self.parent = kwargs.get('parent') + self.attached_to_group: bool = False + + self.cog = None + + params = self._get_signature_parameters() + self.options: List[Option] = kwargs.get('options') or self._parse_options(params) + + try: + checks = func.__commands_checks__ + checks.reverse() + except AttributeError: + checks = kwargs.get('checks', []) + + self.checks = checks + + self._before_invoke = None + self._after_invoke = None + + # Permissions + self.default_permission = kwargs.get("default_permission", True) + self.permissions: List[Permission] = getattr(func, "__app_cmd_perms__", []) + kwargs.get("permissions", []) + if self.permissions and self.default_permission: + self.default_permission = False + + + def _parse_options(self, params) -> List[Option]: + final_options = [] + + if list(params.items())[0][0] == "self": + temp = list(params.items()) + temp.pop(0) + params = dict(temp) + params = iter(params.items()) + + # next we have the 'ctx' as the next parameter + try: + next(params) + except StopIteration: + raise ClientException( + f'Callback for {self.name} command is missing "ctx" parameter.' + ) + + final_options = [] + + for p_name, p_obj in params: + + option = p_obj.annotation + if option == inspect.Parameter.empty: + option = str + + if self._is_typing_union(option): + if self._is_typing_optional(option): + option = Option( + option.__args__[0], "No description provided", required=False + ) + else: + option = Option( + option.__args__, "No description provided" + ) + + if not isinstance(option, Option): + option = Option(option, "No description provided") + if p_obj.default != inspect.Parameter.empty: + option.required = False + + option.default = option.default if option.default is not None else p_obj.default + + if option.default == inspect.Parameter.empty: + option.default = None + + if option.name is None: + option.name = p_name + option._parameter_name = p_name + + validate_chat_input_name(option.name) + validate_chat_input_description(option.description) + + final_options.append(option) + + return final_options + + def _is_typing_union(self, annotation): + return ( + getattr(annotation, '__origin__', None) is Union + or type(annotation) is getattr(types, "UnionType", Union) + ) # type: ignore + + def _is_typing_optional(self, annotation): + return self._is_typing_union(annotation) and type(None) in annotation.__args__ # type: ignore + + @property + def is_subcommand(self) -> bool: + return self.parent is not None + + def to_dict(self) -> Dict: + as_dict = { + "name": self.name, + "description": self.description, + "options": [o.to_dict() for o in self.options], + "default_permission": self.default_permission, + } + if self.is_subcommand: + as_dict["type"] = SlashCommandOptionType.sub_command.value + + return as_dict + + def __eq__(self, other) -> bool: + return ( + isinstance(other, SlashCommand) + and other.name == self.name + and other.description == self.description + ) + + async def _invoke(self, ctx: ApplicationContext) -> None: + # TODO: Parse the args better + kwargs = {} + for arg in ctx.interaction.data.get("options", []): + op = find(lambda x: x.name == arg["name"], self.options) + arg = arg["value"] + + # Checks if input_type is user, role or channel + if ( + SlashCommandOptionType.user.value + <= op.input_type.value + <= SlashCommandOptionType.role.value + ): + if ctx.guild is None and op.input_type.name == "user": + _data = ctx.interaction.data["resolved"]["users"][arg] + _data["id"] = int(arg) + arg = User(state=ctx.interaction._state, data=_data) + else: + name = "member" if op.input_type.name == "user" else op.input_type.name + arg = await get_or_fetch(ctx.guild, name, int(arg), default=int(arg)) + + elif op.input_type == SlashCommandOptionType.mentionable: + arg_id = int(arg) + arg = await get_or_fetch(ctx.guild, "member", arg_id) + if arg is None: + arg = ctx.guild.get_role(arg_id) or arg_id + + elif op.input_type == SlashCommandOptionType.string and op._converter is not None: + arg = await op._converter.convert(ctx, arg) + + kwargs[op._parameter_name] = arg + + for o in self.options: + if o._parameter_name not in kwargs: + kwargs[o._parameter_name] = o.default + + if self.cog is not None: + await self.callback(self.cog, ctx, **kwargs) + elif self.parent is not None and self.attached_to_group is True: + await self.callback(self.parent, ctx, **kwargs) + else: + await self.callback(ctx, **kwargs) + + async def invoke_autocomplete_callback(self, ctx: AutocompleteContext): + values = { i.name: i.default for i in self.options } + + for op in ctx.interaction.data.get("options", []): + if op.get("focused", False): + option = find(lambda o: o.name == op["name"], self.options) + values.update({ + i["name"]:i["value"] + for i in ctx.interaction.data["options"] + }) + ctx.command = self + ctx.focused = option + ctx.value = op.get("value") + ctx.options = values + + if len(inspect.signature(option.autocomplete).parameters) == 2: + instance = getattr(option.autocomplete, "__self__", ctx.cog) + result = option.autocomplete(instance, ctx) + else: + result = option.autocomplete(ctx) + + if asyncio.iscoroutinefunction(option.autocomplete): + result = await result + + choices = [ + o if isinstance(o, OptionChoice) else OptionChoice(o) + for o in result + ][:25] + return await ctx.interaction.response.send_autocomplete_result(choices=choices) + + + def copy(self): + """Creates a copy of this command. + + Returns + -------- + :class:`SlashCommand` + A new instance of this command. + """ + ret = self.__class__(self.callback, **self.__original_kwargs__) + return self._ensure_assignment_on_copy(ret) + + def _ensure_assignment_on_copy(self, other): + other._before_invoke = self._before_invoke + other._after_invoke = self._after_invoke + if self.checks != other.checks: + other.checks = self.checks.copy() + #if self._buckets.valid and not other._buckets.valid: + # other._buckets = self._buckets.copy() + #if self._max_concurrency != other._max_concurrency: + # # _max_concurrency won't be None at this point + # other._max_concurrency = self._max_concurrency.copy() # type: ignore + + try: + other.on_error = self.on_error + except AttributeError: + pass + return other + + def _update_copy(self, kwargs: Dict[str, Any]): + if kwargs: + kw = kwargs.copy() + kw.update(self.__original_kwargs__) + copy = self.__class__(self.callback, **kw) + return self._ensure_assignment_on_copy(copy) + else: + return self.copy() + +channel_type_map = { + 'TextChannel': ChannelType.text, + 'VoiceChannel': ChannelType.voice, + 'StageChannel': ChannelType.stage_voice, + 'CategoryChannel': ChannelType.category +} + +class Option: + def __init__( + self, input_type: Any, /, description: str = None, **kwargs + ) -> None: + self.name: Optional[str] = kwargs.pop("name", None) + self.description = description or "No description provided" + self._converter = None + self.channel_types: List[SlashCommandOptionType] = kwargs.pop("channel_types", []) + if not isinstance(input_type, SlashCommandOptionType): + if hasattr(input_type, "convert"): + self._converter = input_type + input_type = SlashCommandOptionType.string + else: + _type = SlashCommandOptionType.from_datatype(input_type) + if _type == SlashCommandOptionType.channel: + if not isinstance(input_type, tuple): + input_type = (input_type,) + for i in input_type: + if i.__name__ == 'GuildChannel': + continue + + channel_type = channel_type_map[i.__name__] + self.channel_types.append(channel_type) + input_type = _type + self.input_type = input_type + self.required: bool = kwargs.pop("required", True) + self.choices: List[OptionChoice] = [ + o if isinstance(o, OptionChoice) else OptionChoice(o) + for o in kwargs.pop("choices", list()) + ] + self.default = kwargs.pop("default", None) + + if self.input_type == SlashCommandOptionType.integer: + minmax_types = (int, type(None)) + elif self.input_type == SlashCommandOptionType.number: + minmax_types = (int, float, type(None)) + else: + minmax_types = (type(None),) + minmax_typehint = Optional[Union[minmax_types]] # type: ignore + + self.min_value: minmax_typehint = kwargs.pop("min_value", None) + self.max_value: minmax_typehint = kwargs.pop("max_value", None) + + if not (isinstance(self.min_value, minmax_types) or self.min_value is None): + raise TypeError(f"Expected {minmax_typehint} for min_value, got \"{type(self.min_value).__name__}\"") + if not (isinstance(self.max_value, minmax_types) or self.min_value is None): + raise TypeError(f"Expected {minmax_typehint} for max_value, got \"{type(self.max_value).__name__}\"") + + self.autocomplete = kwargs.pop("autocomplete", None) + + def to_dict(self) -> Dict: + as_dict = { + "name": self.name, + "description": self.description, + "type": self.input_type.value, + "required": self.required, + "choices": [c.to_dict() for c in self.choices], + "autocomplete": bool(self.autocomplete) + } + if self.channel_types: + as_dict["channel_types"] = [t.value for t in self.channel_types] + if self.min_value is not None: + as_dict["min_value"] = self.min_value + if self.max_value is not None: + as_dict["max_value"] = self.max_value + + return as_dict + + + def __repr__(self): + return f"" + + +class OptionChoice: + r"""Represents an option choice. + + Attributes + ----------- + name: :class:`str` + The name of the option choice. + value: Optional[Union[:class:`str`, :class:`int`, :class:`float`]] + The value of the option choice. If ``None`` is passed, this will be set to the name of the option choice. + """ + def __init__(self, name: str, value: Optional[Union[str, int, float]] = None): + self.name = name + self.value = value or name + + def to_dict(self) -> Dict[str, Union[str, int, float]]: + return {"name": self.name, "value": self.value} + +def option(name, type=None, **kwargs): + """A decorator that can be used instead of typehinting Option""" + def decor(func): + nonlocal type + type = type or func.__annotations__.get(name, str) + func.__annotations__[name] = Option(type, **kwargs) + return func + return decor + +class SlashCommandGroup(ApplicationCommand, Option): + r"""A class that implements the protocol for a slash command group. + + These can be created manually, but they should be created via the + decorator or functional interface. + + Attributes + ----------- + name: :class:`str` + The name of the command. + description: Optional[:class:`str`] + The description for the command. + guild_ids: Optional[List[:class:`int`]] + The ids of the guilds where this command will be registered. + parent: Optional[:class:`SlashCommandGroup`] + The parent group that this group belongs to. ``None`` if there + isn't one. + subcommands: List[Union[:class:`SlashCommand`, :class:`SlashCommandGroup`]] + The list of all subcommands under this group. + cog: Optional[:class:`Cog`] + The cog that this command belongs to. ``None`` if there isn't one. + checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]] + A list of predicates that verifies if the command could be executed + with the given :class:`.ApplicationContext` as the sole parameter. If an exception + is necessary to be thrown to signal failure, then one inherited from + :exc:`.CommandError` should be used. Note that if the checks fail then + :exc:`.CheckFailure` exception is raised to the :func:`.on_application_command_error` + event. + """ + type = 1 + + def __new__(cls, *args, **kwargs) -> SlashCommandGroup: + self = super().__new__(cls) + self.__original_kwargs__ = kwargs.copy() + + self.__initial_commands__ = [] + for i, c in cls.__dict__.items(): + if isinstance(c, type) and SlashCommandGroup in c.__bases__: + c = c( + c.__name__, + ( + inspect.cleandoc(cls.__doc__).splitlines()[0] + if cls.__doc__ is not None + else "No description provided" + ) + ) + if isinstance(c, (SlashCommand, SlashCommandGroup)): + c.parent = self + c.attached_to_group = True + self.__initial_commands__.append(c) + + return self + + def __init__( + self, + name: str, + description: str, + guild_ids: Optional[List[int]] = None, + parent: Optional[SlashCommandGroup] = None, + **kwargs + ) -> None: + validate_chat_input_name(name) + validate_chat_input_description(description) + super().__init__( + SlashCommandOptionType.sub_command_group, + name=name, + description=description, + ) + self.subcommands: List[Union[SlashCommand, SlashCommandGroup]] = self.__initial_commands__ + self.guild_ids = guild_ids + self.parent = parent + self.checks = [] + + self._before_invoke = None + self._after_invoke = None + self.cog = None + + # Permissions + self.default_permission = kwargs.get("default_permission", True) + self.permissions: List[Permission] = kwargs.get("permissions", []) + if self.permissions and self.default_permission: + self.default_permission = False + + def to_dict(self) -> Dict: + as_dict = { + "name": self.name, + "description": self.description, + "options": [c.to_dict() for c in self.subcommands], + "default_permission": self.default_permission, + } + + if self.parent is not None: + as_dict["type"] = self.input_type.value + + return as_dict + + def command(self, **kwargs) -> SlashCommand: + def wrap(func) -> SlashCommand: + command = SlashCommand(func, parent=self, **kwargs) + self.subcommands.append(command) + return command + + return wrap + + def create_subgroup(self, name, description) -> SlashCommandGroup: + if self.parent is not None: + # TODO: Improve this error message + raise Exception("Subcommands can only be nested once") + + sub_command_group = SlashCommandGroup(name, description, parent=self) + self.subcommands.append(sub_command_group) + return sub_command_group + + def subgroup( + self, + name: str, + description: str = None, + guild_ids: Optional[List[int]] = None, + ) -> Callable[[Type[SlashCommandGroup]], SlashCommandGroup]: + """A shortcut decorator that initializes the provided subclass of :class:`.SlashCommandGroup` + as a subgroup. + + .. versionadded:: 2.0 + + Parameters + ---------- + name: :class:`str` + The name of the group to create. + description: Optional[:class:`str`] + The description of the group to create. + guild_ids: Optional[List[:class:`int`]] + A list of the IDs of each guild this group should be added to, making it a guild command. + This will be a global command if ``None`` is passed. + + Returns + -------- + Callable[[Type[SlashCommandGroup]], SlashCommandGroup] + The slash command group that was created. + """ + def inner(cls: Type[SlashCommandGroup]) -> SlashCommandGroup: + group = cls( + name, + description or ( + inspect.cleandoc(cls.__doc__).splitlines()[0] + if cls.__doc__ is not None + else "No description provided" + ), + guild_ids=guild_ids, + parent=self, + ) + self.subcommands.append(group) + return group + return inner + + async def _invoke(self, ctx: ApplicationContext) -> None: + option = ctx.interaction.data["options"][0] + command = find(lambda x: x.name == option["name"], self.subcommands) + ctx.interaction.data = option + await command.invoke(ctx) + + async def invoke_autocomplete_callback(self, ctx: AutocompleteContext) -> None: + option = ctx.interaction.data["options"][0] + command = find(lambda x: x.name == option["name"], self.subcommands) + ctx.interaction.data = option + await command.invoke_autocomplete_callback(ctx) + + def copy(self): + """Creates a copy of this command group. + + Returns + -------- + :class:`SlashCommandGroup` + A new instance of this command group. + """ + ret = self.__class__( + name=self.name, + description=self.description, + **self.__original_kwargs__, + ) + return self._ensure_assignment_on_copy(ret) + + def _ensure_assignment_on_copy(self, other): + other.parent = self.parent + + other._before_invoke = self._before_invoke + other._after_invoke = self._after_invoke + + if self.subcommands != other.subcommands: + other.subcommands = self.subcommands.copy() + + if self.checks != other.checks: + other.checks = self.checks.copy() + + return other + + def _update_copy(self, kwargs: Dict[str, Any]): + if kwargs: + kw = kwargs.copy() + kw.update(self.__original_kwargs__) + copy = self.__class__(self.callback, **kw) + return self._ensure_assignment_on_copy(copy) + else: + return self.copy() + + def _set_cog(self, cog): + self.cog = cog + for subcommand in self.subcommands: + subcommand._set_cog(cog) + + + +class ContextMenuCommand(ApplicationCommand): + r"""A class that implements the protocol for context menu commands. + + These are not created manually, instead they are created via the + decorator or functional interface. + + Attributes + ----------- + name: :class:`str` + The name of the command. + callback: :ref:`coroutine ` + The coroutine that is executed when the command is called. + guild_ids: Optional[List[:class:`int`]] + The ids of the guilds where this command will be registered. + cog: Optional[:class:`Cog`] + The cog that this command belongs to. ``None`` if there isn't one. + checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]] + A list of predicates that verifies if the command could be executed + with the given :class:`.ApplicationContext` as the sole parameter. If an exception + is necessary to be thrown to signal failure, then one inherited from + :exc:`.CommandError` should be used. Note that if the checks fail then + :exc:`.CheckFailure` exception is raised to the :func:`.on_application_command_error` + event. + """ + def __new__(cls, *args, **kwargs) -> ContextMenuCommand: + self = super().__new__(cls) + + self.__original_kwargs__ = kwargs.copy() + return self + + def __init__(self, func: Callable, *args, **kwargs) -> None: + if not asyncio.iscoroutinefunction(func): + raise TypeError("Callback must be a coroutine.") + self.callback = func + + self.guild_ids: Optional[List[int]] = kwargs.get("guild_ids", None) + + # Discord API doesn't support setting descriptions for context menu commands + # so it must be empty + self.description = "" + self.name: str = kwargs.pop("name", func.__name__) + if not isinstance(self.name, str): + raise TypeError("Name of a command must be a string.") + + self.cog = None + + try: + checks = func.__commands_checks__ + checks.reverse() + except AttributeError: + checks = kwargs.get('checks', []) + + self.checks = checks + self._before_invoke = None + self._after_invoke = None + + self.validate_parameters() + + # Context Menu commands don't have permissions + self.permissions = [] + + # Context Menu commands can't have parents + self.parent = None + + def validate_parameters(self): + params = self._get_signature_parameters() + if list(params.items())[0][0] == "self": + temp = list(params.items()) + temp.pop(0) + params = dict(temp) + params = iter(params) + + # next we have the 'ctx' as the next parameter + try: + next(params) + except StopIteration: + raise ClientException( + f'Callback for {self.name} command is missing "ctx" parameter.' + ) + + # next we have the 'user/message' as the next parameter + try: + next(params) + except StopIteration: + cmd = "user" if type(self) == UserCommand else "message" + raise ClientException( + f'Callback for {self.name} command is missing "{cmd}" parameter.' + ) + + # next there should be no more parameters + try: + next(params) + raise ClientException( + f"Callback for {self.name} command has too many parameters." + ) + except StopIteration: + pass + + def qualified_name(self): + return self.name + + def to_dict(self) -> Dict[str, Union[str, int]]: + return {"name": self.name, "description": self.description, "type": self.type} + + +class UserCommand(ContextMenuCommand): + r"""A class that implements the protocol for user context menu commands. + + These are not created manually, instead they are created via the + decorator or functional interface. + + Attributes + ----------- + name: :class:`str` + The name of the command. + callback: :ref:`coroutine ` + The coroutine that is executed when the command is called. + guild_ids: Optional[List[:class:`int`]] + The ids of the guilds where this command will be registered. + cog: Optional[:class:`Cog`] + The cog that this command belongs to. ``None`` if there isn't one. + checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]] + A list of predicates that verifies if the command could be executed + with the given :class:`.ApplicationContext` as the sole parameter. If an exception + is necessary to be thrown to signal failure, then one inherited from + :exc:`.CommandError` should be used. Note that if the checks fail then + :exc:`.CheckFailure` exception is raised to the :func:`.on_application_command_error` + event. + """ + type = 2 + + def __new__(cls, *args, **kwargs) -> UserCommand: + self = super().__new__(cls) + + self.__original_kwargs__ = kwargs.copy() + return self + + async def _invoke(self, ctx: ApplicationContext) -> None: + if "members" not in ctx.interaction.data["resolved"]: + _data = ctx.interaction.data["resolved"]["users"] + for i, v in _data.items(): + v["id"] = int(i) + user = v + target = User(state=ctx.interaction._state, data=user) + else: + _data = ctx.interaction.data["resolved"]["members"] + for i, v in _data.items(): + v["id"] = int(i) + member = v + _data = ctx.interaction.data["resolved"]["users"] + for i, v in _data.items(): + v["id"] = int(i) + user = v + member["user"] = user + target = Member( + data=member, + guild=ctx.interaction._state._get_guild(ctx.interaction.guild_id), + state=ctx.interaction._state, + ) + + if self.cog is not None: + await self.callback(self.cog, ctx, target) + else: + await self.callback(ctx, target) + + def copy(self): + """Creates a copy of this command. + + Returns + -------- + :class:`UserCommand` + A new instance of this command. + """ + ret = self.__class__(self.callback, **self.__original_kwargs__) + return self._ensure_assignment_on_copy(ret) + + def _ensure_assignment_on_copy(self, other): + other._before_invoke = self._before_invoke + other._after_invoke = self._after_invoke + if self.checks != other.checks: + other.checks = self.checks.copy() + #if self._buckets.valid and not other._buckets.valid: + # other._buckets = self._buckets.copy() + #if self._max_concurrency != other._max_concurrency: + # # _max_concurrency won't be None at this point + # other._max_concurrency = self._max_concurrency.copy() # type: ignore + + try: + other.on_error = self.on_error + except AttributeError: + pass + return other + + def _update_copy(self, kwargs: Dict[str, Any]): + if kwargs: + kw = kwargs.copy() + kw.update(self.__original_kwargs__) + copy = self.__class__(self.callback, **kw) + return self._ensure_assignment_on_copy(copy) + else: + return self.copy() + + +class MessageCommand(ContextMenuCommand): + r"""A class that implements the protocol for message context menu commands. + + These are not created manually, instead they are created via the + decorator or functional interface. + + Attributes + ----------- + name: :class:`str` + The name of the command. + callback: :ref:`coroutine ` + The coroutine that is executed when the command is called. + guild_ids: Optional[List[:class:`int`]] + The ids of the guilds where this command will be registered. + cog: Optional[:class:`Cog`] + The cog that this command belongs to. ``None`` if there isn't one. + checks: List[Callable[[:class:`.ApplicationContext`], :class:`bool`]] + A list of predicates that verifies if the command could be executed + with the given :class:`.ApplicationContext` as the sole parameter. If an exception + is necessary to be thrown to signal failure, then one inherited from + :exc:`.CommandError` should be used. Note that if the checks fail then + :exc:`.CheckFailure` exception is raised to the :func:`.on_application_command_error` + event. + """ + type = 3 + + def __new__(cls, *args, **kwargs) -> MessageCommand: + self = super().__new__(cls) + + self.__original_kwargs__ = kwargs.copy() + return self + + async def _invoke(self, ctx: ApplicationContext): + _data = ctx.interaction.data["resolved"]["messages"] + for i, v in _data.items(): + v["id"] = int(i) + message = v + channel = ctx.interaction._state.get_channel(int(message["channel_id"])) + if channel is None: + data = await ctx.interaction._state.http.start_private_message( + int(message["author"]["id"]) + ) + channel = ctx.interaction._state.add_dm_channel(data) + + target = Message(state=ctx.interaction._state, channel=channel, data=message) + + if self.cog is not None: + await self.callback(self.cog, ctx, target) + else: + await self.callback(ctx, target) + + def copy(self): + """Creates a copy of this command. + + Returns + -------- + :class:`MessageCommand` + A new instance of this command. + """ + ret = self.__class__(self.callback, **self.__original_kwargs__) + return self._ensure_assignment_on_copy(ret) + + def _ensure_assignment_on_copy(self, other): + other._before_invoke = self._before_invoke + other._after_invoke = self._after_invoke + if self.checks != other.checks: + other.checks = self.checks.copy() + #if self._buckets.valid and not other._buckets.valid: + # other._buckets = self._buckets.copy() + #if self._max_concurrency != other._max_concurrency: + # # _max_concurrency won't be None at this point + # other._max_concurrency = self._max_concurrency.copy() # type: ignore + + try: + other.on_error = self.on_error + except AttributeError: + pass + return other + + def _update_copy(self, kwargs: Dict[str, Any]): + if kwargs: + kw = kwargs.copy() + kw.update(self.__original_kwargs__) + copy = self.__class__(self.callback, **kw) + return self._ensure_assignment_on_copy(copy) + else: + return self.copy() + +def slash_command(**kwargs): + """Decorator for slash commands that invokes :func:`application_command`. + .. versionadded:: 2.0 + Returns + -------- + Callable[..., :class:`SlashCommand`] + A decorator that converts the provided method into a :class:`.SlashCommand`. + """ + return application_command(cls=SlashCommand, **kwargs) + +def user_command(**kwargs): + """Decorator for user commands that invokes :func:`application_command`. + .. versionadded:: 2.0 + Returns + -------- + Callable[..., :class:`UserCommand`] + A decorator that converts the provided method into a :class:`.UserCommand`. + """ + return application_command(cls=UserCommand, **kwargs) + +def message_command(**kwargs): + """Decorator for message commands that invokes :func:`application_command`. + .. versionadded:: 2.0 + Returns + -------- + Callable[..., :class:`MessageCommand`] + A decorator that converts the provided method into a :class:`.MessageCommand`. + """ + return application_command(cls=MessageCommand, **kwargs) + +def application_command(cls=SlashCommand, **attrs): + """A decorator that transforms a function into an :class:`.ApplicationCommand`. More specifically, + usually one of :class:`.SlashCommand`, :class:`.UserCommand`, or :class:`.MessageCommand`. The exact class + depends on the ``cls`` parameter. + By default the ``description`` attribute is received automatically from the + docstring of the function and is cleaned up with the use of + ``inspect.cleandoc``. If the docstring is ``bytes``, then it is decoded + into :class:`str` using utf-8 encoding. + The ``name`` attribute also defaults to the function name unchanged. + .. versionadded:: 2.0 + Parameters + ----------- + cls: :class:`.ApplicationCommand` + The class to construct with. By default this is :class:`.SlashCommand`. + You usually do not change this. + attrs + Keyword arguments to pass into the construction of the class denoted + by ``cls``. + Raises + ------- + TypeError + If the function is not a coroutine or is already a command. + """ + + def decorator(func: Callable) -> cls: + if isinstance(func, ApplicationCommand): + func = func.callback + elif not callable(func): + raise TypeError( + "func needs to be a callable or a subclass of ApplicationCommand." + ) + return cls(func, **attrs) + + return decorator + +def command(**kwargs): + """There is an alias for :meth:`application_command`. + .. note:: + This decorator is overridden by :func:`commands.command`. + .. versionadded:: 2.0 + Returns + -------- + Callable[..., :class:`ApplicationCommand`] + A decorator that converts the provided method into an :class:`.ApplicationCommand`. + """ + return application_command(**kwargs) + + +docs = "https://discord.com/developers/docs" + + +# Validation +def validate_chat_input_name(name: Any): + # Must meet the regex ^[\w-]{1,32}$ + if not isinstance(name, str): + raise TypeError(f"Chat input command names and options must be of type str. Received {name}") + if not re.match(r"^[\w-]{1,32}$", name): + raise ValidationError( + r'Chat input command names and options must follow the regex "^[\w-]{1,32}$". For more information, see ' + f"{docs}/interactions/application-commands#application-command-object-application-command-naming. Received " + f"{name}" + ) + if not 1 <= len(name) <= 32: + raise ValidationError( + f"Chat input command names and options must be 1-32 characters long. Received {name}" + ) + if not name.lower() == name: # Can't use islower() as it fails if none of the chars can be lower. See #512. + raise ValidationError(f"Chat input command names and options must be lowercase. Received {name}") + + +def validate_chat_input_description(description: Any): + if not isinstance(description, str): + raise TypeError(f"Command description must be of type str. Received {description}") + if not 1 <= len(description) <= 100: + raise ValidationError( + f"Command description must be 1-100 characters long. Received {description}" + ) diff --git a/build/lib/discord/commands/context.py b/build/lib/discord/commands/context.py new file mode 100644 index 0000000000..0402df33e8 --- /dev/null +++ b/build/lib/discord/commands/context.py @@ -0,0 +1,200 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 TYPE_CHECKING, Optional, Union + +import discord.abc + +if TYPE_CHECKING: + import discord + from discord import Bot + from discord.state import ConnectionState + + from .commands import ApplicationCommand, Option + from ..cog import Cog + +from ..guild import Guild +from ..interactions import Interaction, InteractionResponse +from ..member import Member +from ..message import Message +from ..user import User +from ..utils import cached_property + +__all__ = ( + "ApplicationContext", + "AutocompleteContext" +) + +class ApplicationContext(discord.abc.Messageable): + """Represents a Discord application command interaction context. + + This class is not created manually and is instead passed to application + commands as the first parameter. + + .. versionadded:: 2.0 + + Attributes + ----------- + bot: :class:`.Bot` + The bot that the command belongs to. + interaction: :class:`.Interaction` + The interaction object that invoked the command. + command: :class:`.ApplicationCommand` + The command that this context belongs to. + """ + + def __init__(self, bot: Bot, interaction: Interaction): + self.bot = bot + self.interaction = interaction + + # below attributes will be set after initialization + self.command: ApplicationCommand = None # type: ignore + self.focused: Option = None # type: ignore + self.value: str = None # type: ignore + self.options: dict = None # type: ignore + + self._state: ConnectionState = self.interaction._state + + async def _get_channel(self) -> discord.abc.Messageable: + return self.channel + + @cached_property + def channel(self): + return self.interaction.channel + + @cached_property + def channel_id(self) -> Optional[int]: + return self.interaction.channel_id + + @cached_property + def guild(self) -> Optional[Guild]: + return self.interaction.guild + + @cached_property + def guild_id(self) -> Optional[int]: + return self.interaction.guild_id + + @cached_property + def me(self) -> Union[Member, User]: + return self.guild.me if self.guild is not None else self.bot.user + + @cached_property + def message(self) -> Optional[Message]: + return self.interaction.message + + @cached_property + def user(self) -> Optional[Union[Member, User]]: + return self.interaction.user + + @cached_property + def author(self) -> Optional[Union[Member, User]]: + return self.user + + @property + def voice_client(self): + if self.guild is None: + return None + + return self.guild.voice_client + + @cached_property + def response(self) -> InteractionResponse: + return self.interaction.response + + @property + def respond(self): + return self.followup.send if self.response.is_done() else self.interaction.response.send_message + + @property + def defer(self): + return self.interaction.response.defer + + @property + def followup(self): + return self.interaction.followup + + async def delete(self): + """Calls :attr:`~discord.commands.ApplicationContext.respond`. + If the response is done, then calls :attr:`~discord.commands.ApplicationContext.respond` first.""" + if not self.response.is_done(): + await self.defer() + + return await self.interaction.delete_original_message() + + @property + def edit(self): + return self.interaction.edit_original_message + + @property + def cog(self) -> Optional[Cog]: + """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist.""" + if self.command is None: + return None + + return self.command.cog + + +class AutocompleteContext: + """Represents context for a slash command's option autocomplete. + + This class is not created manually and is instead passed to an Option's autocomplete callback. + + .. versionadded:: 2.0 + + Attributes + ----------- + bot: :class:`.Bot` + The bot that the command belongs to. + interaction: :class:`.Interaction` + The interaction object that invoked the autocomplete. + command: :class:`.ApplicationCommand` + The command that this context belongs to. + focused: :class:`.Option` + The option the user is currently typing. + value: :class:`.str` + The content of the focused option. + options :class:`.dict` + A name to value mapping of the options that the user has selected before this option. + """ + + __slots__ = ("bot", "interaction", "command", "focused", "value", "options") + + def __init__(self, bot: Bot, interaction: Interaction) -> None: + self.bot = bot + self.interaction = interaction + + self.command: ApplicationCommand = None # type: ignore + self.focused: Option = None # type: ignore + self.value: str = None # type: ignore + self.options: dict = None # type: ignore + + @property + def cog(self) -> Optional[Cog]: + """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist.""" + if self.command is None: + return None + + return self.command.cog diff --git a/build/lib/discord/commands/errors.py b/build/lib/discord/commands/errors.py new file mode 100644 index 0000000000..5a4b47eec8 --- /dev/null +++ b/build/lib/discord/commands/errors.py @@ -0,0 +1,64 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +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 ..errors import DiscordException + +__all__ = ( + "ApplicationCommandError", + "CheckFailure", + "ApplicationCommandInvokeError", +) + +class ApplicationCommandError(DiscordException): + r"""The base exception type for all application command related errors. + + This inherits from :exc:`discord.DiscordException`. + + This exception and exceptions inherited from it are handled + in a special way as they are caught and passed into a special event + from :class:`.Bot`\, :func:`.on_command_error`. + """ + pass + +class CheckFailure(ApplicationCommandError): + """Exception raised when the predicates in :attr:`.Command.checks` have failed. + + This inherits from :exc:`ApplicationCommandError` + """ + pass + +class ApplicationCommandInvokeError(ApplicationCommandError): + """Exception raised when the command being invoked raised an exception. + + This inherits from :exc:`ApplicationCommandError` + + Attributes + ----------- + original: :exc:`Exception` + The original exception that was raised. You can also get this via + the ``__cause__`` attribute. + """ + def __init__(self, e: Exception) -> None: + self.original: Exception = e + super().__init__(f'Application Command raised an exception: {e.__class__.__name__}: {e}') diff --git a/build/lib/discord/commands/permissions.py b/build/lib/discord/commands/permissions.py new file mode 100644 index 0000000000..f8a09103b8 --- /dev/null +++ b/build/lib/discord/commands/permissions.py @@ -0,0 +1,228 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 Union, Dict, Callable + +__all__ = ( + "Permission", + "has_role", + "has_any_role", + "is_user", + "is_owner", + "permission", +) + +class Permission: + """The class used in the application command decorators + to hash permission data into a dictionary using the + :meth:`to_dict` method to be sent to the discord API later on. + + .. versionadded:: 2.0 + + Attributes + ----------- + id: Union[:class:`int`, :class:`str`] + A string or integer that represents or helps get + the id of the user or role that the permission is tied to. + type: :class:`int` + An integer representing the type of the permission. + permission: :class:`bool` + A boolean representing the permission's value. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def __init__(self, id: Union[int, str], type: int, permission: bool = True, guild_id: int = None): + self.id = id + self.type = type + self.permission = permission + self.guild_id = guild_id + + def to_dict(self) -> Dict[str, Union[int, bool]]: + return {"id": self.id, "type": self.type, "permission": self.permission} + +def permission(role_id: int = None, user_id: int = None, permission: bool = True, guild_id: int = None): + """The method used to specify application command permissions + for specific users or roles using their id. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + role_id: :class:`int` + An integer which represents the id of the role that the + permission may be tied to. + user_id: :class:`int` + An integer which represents the id of the user that the + permission may be tied to. + permission: :class:`bool` + A boolean representing the permission's value. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def decorator(func: Callable): + if not role_id is None: + app_cmd_perm = Permission(role_id, 1, permission, guild_id) + elif not user_id is None: + app_cmd_perm = Permission(user_id, 2, permission, guild_id) + else: + raise ValueError("role_id or user_id must be specified!") + + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + +def has_role(item: Union[int, str], guild_id: int = None): + """The method used to specify application command role restrictions. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + item: Union[:class:`int`, :class:`str`] + An integer or string that represent the id or name of the role + that the permission is tied to. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + app_cmd_perm = Permission(item, 1, True, guild_id) #{"id": item, "type": 1, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + +def has_any_role(*items: Union[int, str], guild_id: int = None): + """The method used to specify multiple application command role restrictions, + The application command runs if the invoker has **any** of the specified roles. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + *items: Union[:class:`int`, :class:`str`] + The integers or strings that represent the ids or names of the roles + that the permission is tied to. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + for item in items: + app_cmd_perm = Permission(item, 1, True, guild_id) #{"id": item, "type": 1, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + +def is_user(user: int, guild_id: int = None): + """The method used to specify application command user restrictions. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + user: :class:`int` + An integer that represent the id of the user that the permission is tied to. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + app_cmd_perm = Permission(user, 2, True, guild_id) #{"id": user, "type": 2, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + +def is_owner(guild_id: int = None): + """The method used to limit application commands exclusively + to the owner of the bot. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + app_cmd_perm = Permission("owner", 2, True, guild_id) #{"id": "owner", "type": 2, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator diff --git a/build/lib/discord/components.py b/build/lib/discord/components.py new file mode 100644 index 0000000000..b6719e7c1b --- /dev/null +++ b/build/lib/discord/components.py @@ -0,0 +1,384 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union +from .enums import try_enum, ComponentType, ButtonStyle +from .utils import get_slots, MISSING +from .partial_emoji import PartialEmoji, _EmojiTag + +if TYPE_CHECKING: + from .types.components import ( + Component as ComponentPayload, + ButtonComponent as ButtonComponentPayload, + SelectMenu as SelectMenuPayload, + SelectOption as SelectOptionPayload, + ActionRow as ActionRowPayload, + ) + from .emoji import Emoji + + +__all__ = ( + 'Component', + 'ActionRow', + 'Button', + 'SelectMenu', + 'SelectOption', +) + +C = TypeVar('C', bound='Component') + + +class Component: + """Represents a Discord Bot UI Kit Component. + + Currently, the only components supported by Discord are: + + - :class:`ActionRow` + - :class:`Button` + - :class:`SelectMenu` + + This class is abstract and cannot be instantiated. + + .. versionadded:: 2.0 + + Attributes + ------------ + type: :class:`ComponentType` + The type of component. + """ + + __slots__: Tuple[str, ...] = ('type',) + + __repr_info__: ClassVar[Tuple[str, ...]] + type: ComponentType + + def __repr__(self) -> str: + attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__repr_info__) + return f'<{self.__class__.__name__} {attrs}>' + + @classmethod + def _raw_construct(cls: Type[C], **kwargs) -> C: + self: C = cls.__new__(cls) + for slot in get_slots(cls): + try: + value = kwargs[slot] + except KeyError: + pass + else: + setattr(self, slot, value) + return self + + def to_dict(self) -> Dict[str, Any]: + raise NotImplementedError + + +class ActionRow(Component): + """Represents a Discord Bot UI Kit Action Row. + + This is a component that holds up to 5 children components in a row. + + This inherits from :class:`Component`. + + .. versionadded:: 2.0 + + Attributes + ------------ + type: :class:`ComponentType` + The type of component. + children: List[:class:`Component`] + The children components that this holds, if any. + """ + + __slots__: Tuple[str, ...] = ('children',) + + __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + + def __init__(self, data: ComponentPayload): + self.type: ComponentType = try_enum(ComponentType, data['type']) + self.children: List[Component] = [_component_factory(d) for d in data.get('components', [])] + + def to_dict(self) -> ActionRowPayload: + return { + 'type': int(self.type), + 'components': [child.to_dict() for child in self.children], + } # type: ignore + + +class Button(Component): + """Represents a button from the Discord Bot UI Kit. + + 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 + + Attributes + ----------- + style: :class:`.ButtonStyle` + The style of the button. + custom_id: Optional[:class:`str`] + The ID of the button that gets received during an interaction. + If this button is for a URL, it does not have a custom ID. + url: Optional[:class:`str`] + The URL this button sends you to. + disabled: :class:`bool` + Whether the button is disabled or not. + label: Optional[:class:`str`] + The label of the button, if any. + emoji: Optional[:class:`PartialEmoji`] + The emoji of the button, if available. + """ + + __slots__: Tuple[str, ...] = ( + 'style', + 'custom_id', + 'url', + 'disabled', + 'label', + 'emoji', + ) + + __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + + def __init__(self, data: ButtonComponentPayload): + self.type: ComponentType = try_enum(ComponentType, data['type']) + self.style: ButtonStyle = try_enum(ButtonStyle, data['style']) + self.custom_id: Optional[str] = data.get('custom_id') + self.url: Optional[str] = data.get('url') + self.disabled: bool = data.get('disabled', False) + self.label: Optional[str] = data.get('label') + self.emoji: Optional[PartialEmoji] + try: + self.emoji = PartialEmoji.from_dict(data['emoji']) + except KeyError: + self.emoji = None + + def to_dict(self) -> ButtonComponentPayload: + payload = { + 'type': 2, + 'style': int(self.style), + 'label': self.label, + 'disabled': self.disabled, + } + if self.custom_id: + payload['custom_id'] = self.custom_id + + if self.url: + payload['url'] = self.url + + if self.emoji: + payload['emoji'] = self.emoji.to_dict() + + return payload # type: ignore + + +class SelectMenu(Component): + """Represents a select menu from the Discord Bot UI Kit. + + A select menu is functionally the same as a dropdown, however + 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 + + Attributes + ------------ + custom_id: Optional[:class:`str`] + The ID of the select menu that gets received during an interaction. + placeholder: Optional[:class:`str`] + The placeholder text that is shown if nothing is selected, if any. + min_values: :class:`int` + The minimum number of items that must be chosen for this select menu. + Defaults to 1 and must be between 1 and 25. + max_values: :class:`int` + The maximum number of items that must be chosen for this select menu. + Defaults to 1 and must be between 1 and 25. + options: List[:class:`SelectOption`] + A list of options that can be selected in this menu. + disabled: :class:`bool` + Whether the select is disabled or not. + """ + + __slots__: Tuple[str, ...] = ( + 'custom_id', + 'placeholder', + 'min_values', + 'max_values', + 'options', + 'disabled', + ) + + __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + + def __init__(self, data: SelectMenuPayload): + self.type = ComponentType.select + self.custom_id: str = data['custom_id'] + self.placeholder: Optional[str] = data.get('placeholder') + self.min_values: int = data.get('min_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.disabled: bool = data.get('disabled', False) + + def to_dict(self) -> SelectMenuPayload: + payload: SelectMenuPayload = { + 'type': self.type.value, + 'custom_id': self.custom_id, + 'min_values': self.min_values, + 'max_values': self.max_values, + 'options': [op.to_dict() for op in self.options], + 'disabled': self.disabled, + } + + if self.placeholder: + payload['placeholder'] = self.placeholder + + return payload + + +class SelectOption: + """Represents a select menu's option. + + These can be created by users. + + .. versionadded:: 2.0 + + Attributes + ----------- + label: :class:`str` + The label of the option. This is displayed to users. + Can only be up to 100 characters. + value: :class:`str` + The value of the option. This is not displayed to users. + If not provided when constructed then it defaults to the + label. Can only be up to 100 characters. + description: Optional[:class:`str`] + An additional description of the option, if any. + Can only be up to 100 characters. + emoji: Optional[Union[:class:`str`, :class:`Emoji`, :class:`PartialEmoji`]] + The emoji of the option, if available. + default: :class:`bool` + Whether this option is selected by default. + """ + + __slots__: Tuple[str, ...] = ( + 'label', + 'value', + 'description', + 'emoji', + 'default', + ) + + def __init__( + self, + *, + label: str, + value: str = MISSING, + description: Optional[str] = None, + emoji: Optional[Union[str, Emoji, PartialEmoji]] = None, + default: bool = False, + ) -> None: + self.label = label + self.value = label if value is MISSING else value + self.description = description + + if emoji is not None: + if isinstance(emoji, str): + emoji = PartialEmoji.from_str(emoji) + elif isinstance(emoji, _EmojiTag): + emoji = emoji._to_partial() + else: + raise TypeError(f'expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__}') + + self.emoji = emoji + self.default = default + + def __repr__(self) -> str: + return ( + f'' + ) + + 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 + def from_dict(cls, data: SelectOptionPayload) -> SelectOption: + try: + emoji = PartialEmoji.from_dict(data['emoji']) + except KeyError: + emoji = None + + return cls( + label=data['label'], + value=data['value'], + description=data.get('description'), + emoji=emoji, + default=data.get('default', False), + ) + + def to_dict(self) -> SelectOptionPayload: + payload: SelectOptionPayload = { + 'label': self.label, + 'value': self.value, + 'default': self.default, + } + + if self.emoji: + payload['emoji'] = self.emoji.to_dict() # type: ignore + + if self.description: + payload['description'] = self.description + + return payload + + +def _component_factory(data: ComponentPayload) -> Component: + component_type = data['type'] + if component_type == 1: + return ActionRow(data) + elif component_type == 2: + return Button(data) # type: ignore + elif component_type == 3: + return SelectMenu(data) # type: ignore + else: + as_enum = try_enum(ComponentType, component_type) + return Component._raw_construct(type=as_enum) diff --git a/build/lib/discord/context_managers.py b/build/lib/discord/context_managers.py new file mode 100644 index 0000000000..7db421fd82 --- /dev/null +++ b/build/lib/discord/context_managers.py @@ -0,0 +1,88 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +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__ = ( + 'Typing', +) + +def _typing_done_callback(fut: asyncio.Future) -> None: + # just retrieve any exception and call it a day + try: + fut.exception() + except (asyncio.CancelledError, Exception): + pass + +class Typing: + def __init__(self, messageable: Messageable) -> None: + self.loop: asyncio.AbstractEventLoop = messageable._state.loop + self.messageable: Messageable = messageable + + async def do_typing(self) -> None: + try: + channel = self._channel + except AttributeError: + channel = await self.messageable._get_channel() + + typing = channel._state.http.send_typing + + while True: + await typing(channel.id) + await asyncio.sleep(5) + + def __enter__(self: TypingT) -> TypingT: + self.task: asyncio.Task = self.loop.create_task(self.do_typing()) + self.task.add_done_callback(_typing_done_callback) + return self + + def __exit__(self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + self.task.cancel() + + async def __aenter__(self: TypingT) -> TypingT: + self._channel = channel = await self.messageable._get_channel() + await channel._state.http.send_typing(channel.id) + return self.__enter__() + + async def __aexit__(self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + self.task.cancel() diff --git a/build/lib/discord/embeds.py b/build/lib/discord/embeds.py new file mode 100644 index 0000000000..80cc14ac13 --- /dev/null +++ b/build/lib/discord/embeds.py @@ -0,0 +1,759 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import datetime +from typing import Any, Dict, Final, List, Mapping, Protocol, TYPE_CHECKING, Type, TypeVar, Union + +from . import utils +from .colour import Colour + +__all__ = ( + 'Embed', +) + + +class _EmptyEmbed: + def __bool__(self) -> bool: + return False + + def __repr__(self) -> str: + return 'Embed.Empty' + + def __len__(self) -> int: + return 0 + + +EmptyEmbed: Final = _EmptyEmbed() + + +class EmbedProxy: + def __init__(self, layer: Dict[str, Any]): + self.__dict__.update(layer) + + def __len__(self) -> int: + return len(self.__dict__) + + def __repr__(self) -> str: + inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_'))) + return f'EmbedProxy({inner})' + + def __getattr__(self, attr: str) -> _EmptyEmbed: + return EmptyEmbed + + +E = TypeVar('E', bound='Embed') + +if TYPE_CHECKING: + from discord.types.embed import Embed as EmbedData, EmbedType + + T = TypeVar('T') + MaybeEmpty = Union[T, _EmptyEmbed] + + class _EmbedFooterProxy(Protocol): + text: MaybeEmpty[str] + icon_url: MaybeEmpty[str] + + class _EmbedFieldProxy(Protocol): + name: MaybeEmpty[str] + value: MaybeEmpty[str] + inline: bool + + class _EmbedMediaProxy(Protocol): + url: MaybeEmpty[str] + proxy_url: MaybeEmpty[str] + height: MaybeEmpty[int] + width: MaybeEmpty[int] + + class _EmbedVideoProxy(Protocol): + url: MaybeEmpty[str] + height: MaybeEmpty[int] + width: MaybeEmpty[int] + + class _EmbedProviderProxy(Protocol): + name: MaybeEmpty[str] + url: MaybeEmpty[str] + + class _EmbedAuthorProxy(Protocol): + name: MaybeEmpty[str] + url: MaybeEmpty[str] + icon_url: MaybeEmpty[str] + proxy_icon_url: MaybeEmpty[str] + + +class Embed: + """Represents a Discord embed. + + .. container:: operations + + .. describe:: len(x) + + Returns the total size of the embed. + Useful for checking if it's within the 6000 character limit. + + .. describe:: bool(b) + + Returns whether the embed has any data set. + + .. versionadded:: 2.0 + + Certain properties return an ``EmbedProxy``, a type + that acts similar to a regular :class:`dict` except using dotted access, + e.g. ``embed.author.icon_url``. If the attribute + is invalid or empty, then a special sentinel value is returned, + :attr:`Embed.Empty`. + + For ease of use, all parameters that expect a :class:`str` are implicitly + casted to :class:`str` for you. + + Attributes + ----------- + title: :class:`str` + The title of the embed. + This can be set during initialisation. + type: :class:`str` + The type of embed. Usually "rich". + This can be set during initialisation. + Possible strings for embed types can be found on discord's + `api docs `_ + description: :class:`str` + The description of the embed. + This can be set during initialisation. + url: :class:`str` + The URL of the embed. + This can be set during initialisation. + timestamp: :class:`datetime.datetime` + The timestamp of the embed content. This is an aware datetime. + If a naive datetime is passed, it is converted to an aware + datetime with the local timezone. + colour: Union[:class:`Colour`, :class:`int`] + The colour code of the embed. Aliased to ``color`` as well. + This can be set during initialisation. + Empty + A special sentinel value used by ``EmbedProxy`` and this class + to denote that the value or attribute is empty. + """ + + __slots__ = ( + 'title', + 'url', + 'type', + '_timestamp', + '_colour', + '_footer', + '_image', + '_thumbnail', + '_video', + '_provider', + '_author', + '_fields', + 'description', + ) + + Empty: Final = EmptyEmbed + + def __init__( + self, + *, + colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, + color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, + title: MaybeEmpty[Any] = EmptyEmbed, + type: EmbedType = 'rich', + url: MaybeEmpty[Any] = EmptyEmbed, + description: MaybeEmpty[Any] = EmptyEmbed, + timestamp: datetime.datetime = None, + ): + + self.colour = colour if colour is not EmptyEmbed else color + self.title = title + self.type = type + self.url = url + self.description = description + + if self.title is not EmptyEmbed: + self.title = str(self.title) + + if self.description is not EmptyEmbed: + self.description = str(self.description) + + if self.url is not EmptyEmbed: + self.url = str(self.url) + + if timestamp: + self.timestamp = timestamp + + @classmethod + def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E: + """Converts a :class:`dict` to a :class:`Embed` provided it is in the + format that Discord expects it to be in. + + You can find out about this format in the `official Discord documentation`__. + + .. _DiscordDocs: https://discord.com/developers/docs/resources/channel#embed-object + + __ DiscordDocs_ + + Parameters + ----------- + data: :class:`dict` + The dictionary to convert into an embed. + """ + # we are bypassing __init__ here since it doesn't apply here + self: E = cls.__new__(cls) + + # fill in the basic fields + + self.title = data.get('title', EmptyEmbed) + self.type = data.get('type', EmptyEmbed) + self.description = data.get('description', EmptyEmbed) + self.url = data.get('url', EmptyEmbed) + + if self.title is not EmptyEmbed: + self.title = str(self.title) + + if self.description is not EmptyEmbed: + self.description = str(self.description) + + if self.url is not EmptyEmbed: + self.url = str(self.url) + + # try to fill in the more rich fields + + try: + self._colour = Colour(value=data['color']) + except KeyError: + pass + + try: + self._timestamp = utils.parse_time(data['timestamp']) + except KeyError: + pass + + for attr in ('thumbnail', 'video', 'provider', 'author', 'fields', 'image', 'footer'): + try: + value = data[attr] + except KeyError: + continue + else: + setattr(self, '_' + attr, value) + + return self + + def copy(self: E) -> E: + """Returns a shallow copy of the embed.""" + return self.__class__.from_dict(self.to_dict()) + + def __len__(self) -> int: + total = len(self.title) + len(self.description) + for field in getattr(self, '_fields', []): + total += len(field['name']) + len(field['value']) + + try: + footer_text = self._footer['text'] + except (AttributeError, KeyError): + pass + else: + total += len(footer_text) + + try: + author = self._author + except AttributeError: + pass + else: + total += len(author['name']) + + return total + + def __bool__(self) -> bool: + return any( + ( + self.title, + self.url, + self.description, + self.colour, + self.fields, + self.timestamp, + self.author, + self.thumbnail, + self.footer, + self.image, + self.provider, + self.video, + ) + ) + + @property + def colour(self) -> MaybeEmpty[Colour]: + return getattr(self, '_colour', EmptyEmbed) + + @colour.setter + def colour(self, value: Union[int, Colour, _EmptyEmbed]): # type: ignore + if isinstance(value, (Colour, _EmptyEmbed)): + self._colour = value + elif isinstance(value, int): + self._colour = Colour(value=value) + else: + raise TypeError(f'Expected discord.Colour, int, or Embed.Empty but received {value.__class__.__name__} instead.') + + color = colour + + @property + def timestamp(self) -> MaybeEmpty[datetime.datetime]: + return getattr(self, '_timestamp', EmptyEmbed) + + @timestamp.setter + def timestamp(self, value: MaybeEmpty[datetime.datetime]): + if isinstance(value, datetime.datetime): + if value.tzinfo is None: + value = value.astimezone() + self._timestamp = value + elif isinstance(value, _EmptyEmbed): + self._timestamp = value + else: + raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead") + + @property + def footer(self) -> _EmbedFooterProxy: + """Returns an ``EmbedProxy`` denoting the footer contents. + + See :meth:`set_footer` for possible values you can access. + + If the attribute has no value then :attr:`Empty` is returned. + """ + return EmbedProxy(getattr(self, '_footer', {})) # type: ignore + + def set_footer(self: E, *, text: MaybeEmpty[Any] = EmptyEmbed, icon_url: MaybeEmpty[Any] = EmptyEmbed) -> E: + """Sets the footer for the embed content. + + This function returns the class instance to allow for fluent-style + chaining. + + Parameters + ----------- + text: :class:`str` + The footer text. + icon_url: :class:`str` + The URL of the footer icon. Only HTTP(S) is supported. + """ + + self._footer = {} + if text is not EmptyEmbed: + self._footer['text'] = str(text) + + if icon_url is not EmptyEmbed: + self._footer['icon_url'] = str(icon_url) + + return self + + def remove_footer(self: E) -> E: + """Clears embed's footer information. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionadded:: 2.0 + """ + try: + del self._footer + except AttributeError: + pass + + return self + + @property + def image(self) -> _EmbedMediaProxy: + """Returns an ``EmbedProxy`` denoting the image contents. + + Possible attributes you can access are: + + - ``url`` + - ``proxy_url`` + - ``width`` + - ``height`` + + If the attribute has no value then :attr:`Empty` is returned. + """ + return EmbedProxy(getattr(self, '_image', {})) # type: ignore + + def set_image(self: E, *, url: MaybeEmpty[Any]) -> E: + """Sets the image for the embed content. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionchanged:: 1.4 + Passing :attr:`Empty` removes the image. + + Parameters + ----------- + url: :class:`str` + The source URL for the image. Only HTTP(S) is supported. + """ + + if url is EmptyEmbed: + try: + del self._image + except AttributeError: + pass + else: + self._image = { + 'url': str(url), + } + + return self + + def remove_image(self: E) -> E: + """Removes the embed's image. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionadded:: 2.0 + """ + try: + del self._image + except AttributeError: + pass + + return self + + + @property + def thumbnail(self) -> _EmbedMediaProxy: + """Returns an ``EmbedProxy`` denoting the thumbnail contents. + + Possible attributes you can access are: + + - ``url`` + - ``proxy_url`` + - ``width`` + - ``height`` + + If the attribute has no value then :attr:`Empty` is returned. + """ + return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore + + def set_thumbnail(self: E, *, url: MaybeEmpty[Any]) -> E: + """Sets the thumbnail for the embed content. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionchanged:: 1.4 + Passing :attr:`Empty` removes the thumbnail. + + Parameters + ----------- + url: :class:`str` + The source URL for the thumbnail. Only HTTP(S) is supported. + """ + + if url is EmptyEmbed: + try: + del self._thumbnail + except AttributeError: + pass + else: + self._thumbnail = { + 'url': str(url), + } + + return self + + def remove_thumbnail(self: E) -> E: + """Removes the embed's thumbnail. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionadded:: 2.0 + """ + try: + del self._thumbnail + except AttributeError: + pass + + return self + + @property + def video(self) -> _EmbedVideoProxy: + """Returns an ``EmbedProxy`` denoting the video contents. + + Possible attributes include: + + - ``url`` for the video URL. + - ``height`` for the video height. + - ``width`` for the video width. + + If the attribute has no value then :attr:`Empty` is returned. + """ + return EmbedProxy(getattr(self, '_video', {})) # type: ignore + + @property + def provider(self) -> _EmbedProviderProxy: + """Returns an ``EmbedProxy`` denoting the provider contents. + + The only attributes that might be accessed are ``name`` and ``url``. + + If the attribute has no value then :attr:`Empty` is returned. + """ + return EmbedProxy(getattr(self, '_provider', {})) # type: ignore + + @property + def author(self) -> _EmbedAuthorProxy: + """Returns an ``EmbedProxy`` denoting the author contents. + + See :meth:`set_author` for possible values you can access. + + If the attribute has no value then :attr:`Empty` is returned. + """ + return EmbedProxy(getattr(self, '_author', {})) # type: ignore + + def set_author(self: E, *, name: Any, url: MaybeEmpty[Any] = EmptyEmbed, icon_url: MaybeEmpty[Any] = EmptyEmbed) -> E: + """Sets the author for the embed content. + + This function returns the class instance to allow for fluent-style + chaining. + + Parameters + ----------- + name: :class:`str` + The name of the author. + url: :class:`str` + The URL for the author. + icon_url: :class:`str` + The URL of the author icon. Only HTTP(S) is supported. + """ + + self._author = { + 'name': str(name), + } + + if url is not EmptyEmbed: + self._author['url'] = str(url) + + if icon_url is not EmptyEmbed: + self._author['icon_url'] = str(icon_url) + + return self + + def remove_author(self: E) -> E: + """Clears embed's author information. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionadded:: 1.4 + """ + try: + del self._author + except AttributeError: + pass + + return self + + @property + def fields(self) -> List[_EmbedFieldProxy]: + """List[Union[``EmbedProxy``, :attr:`Empty`]]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents. + + See :meth:`add_field` for possible values you can access. + + If the attribute has no value then :attr:`Empty` is returned. + """ + return [EmbedProxy(d) for d in getattr(self, '_fields', [])] # type: ignore + + def add_field(self: E, *, name: Any, value: Any, inline: bool = True) -> E: + """Adds a field to the embed object. + + This function returns the class instance to allow for fluent-style + chaining. + + Parameters + ----------- + name: :class:`str` + The name of the field. + value: :class:`str` + The value of the field. + inline: :class:`bool` + Whether the field should be displayed inline. + """ + + field = { + 'inline': inline, + 'name': str(name), + 'value': str(value), + } + + try: + self._fields.append(field) + except AttributeError: + self._fields = [field] + + return self + + def insert_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = True) -> E: + """Inserts a field before a specified index to the embed. + + This function returns the class instance to allow for fluent-style + chaining. + + .. versionadded:: 1.2 + + Parameters + ----------- + index: :class:`int` + The index of where to insert the field. + name: :class:`str` + The name of the field. + value: :class:`str` + The value of the field. + inline: :class:`bool` + Whether the field should be displayed inline. + """ + + field = { + 'inline': inline, + 'name': str(name), + 'value': str(value), + } + + try: + self._fields.insert(index, field) + except AttributeError: + self._fields = [field] + + return self + + def clear_fields(self) -> None: + """Removes all fields from this embed.""" + try: + self._fields.clear() + except AttributeError: + self._fields = [] + + def remove_field(self, index: int) -> None: + """Removes a field at a specified index. + + If the index is invalid or out of bounds then the error is + silently swallowed. + + .. note:: + + When deleting a field by index, the index of the other fields + shift to fill the gap just like a regular list. + + Parameters + ----------- + index: :class:`int` + The index of the field to remove. + """ + try: + del self._fields[index] + except (AttributeError, IndexError): + pass + + def set_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = True) -> E: + """Modifies a field to the embed object. + + The index must point to a valid pre-existing field. + + This function returns the class instance to allow for fluent-style + chaining. + + Parameters + ----------- + index: :class:`int` + The index of the field to modify. + name: :class:`str` + The name of the field. + value: :class:`str` + The value of the field. + inline: :class:`bool` + Whether the field should be displayed inline. + + Raises + ------- + IndexError + An invalid index was provided. + """ + + try: + field = self._fields[index] + except (TypeError, IndexError, AttributeError): + raise IndexError('field index out of range') + + field['name'] = str(name) + field['value'] = str(value) + field['inline'] = inline + return self + + def to_dict(self) -> EmbedData: + """Converts this embed object into a dict.""" + + # add in the raw data into the dict + # fmt: off + result = { + key[1:]: getattr(self, key) + for key in self.__slots__ + if key[0] == '_' and hasattr(self, key) + } + # fmt: on + + # deal with basic convenience wrappers + + try: + colour = result.pop('colour') + except KeyError: + pass + else: + if colour: + result['color'] = colour.value + + try: + timestamp = result.pop('timestamp') + except KeyError: + pass + else: + if timestamp: + if timestamp.tzinfo: + result['timestamp'] = timestamp.astimezone(tz=datetime.timezone.utc).isoformat() + else: + result['timestamp'] = timestamp.replace(tzinfo=datetime.timezone.utc).isoformat() + + # add in the non raw attribute ones + if self.type: + result['type'] = self.type + + if self.description: + result['description'] = self.description + + if self.url: + result['url'] = self.url + + if self.title: + result['title'] = self.title + + return result # type: ignore diff --git a/build/lib/discord/emoji.py b/build/lib/discord/emoji.py new file mode 100644 index 0000000000..d0324dc96e --- /dev/null +++ b/build/lib/discord/emoji.py @@ -0,0 +1,256 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 Any, Iterator, List, Optional, TYPE_CHECKING, Tuple + +from .asset import Asset, AssetMixin +from .utils import SnowflakeList, snowflake_time, MISSING +from .partial_emoji import _EmojiTag, PartialEmoji +from .user import User + +__all__ = ( + 'Emoji', +) + +if TYPE_CHECKING: + from .types.emoji import Emoji as EmojiPayload + from .guild import Guild + from .state import ConnectionState + from .abc import Snowflake + from .role import Role + from datetime import datetime + + +class Emoji(_EmojiTag, AssetMixin): + """Represents a custom emoji. + + Depending on the way this object was created, some of the attributes can + have a value of ``None``. + + .. container:: operations + + .. describe:: x == y + + Checks if two emoji are the same. + + .. describe:: x != y + + Checks if two emoji are not the same. + + .. describe:: hash(x) + + Return the emoji's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(field, value)`` pairs. This allows this class + to be used as an iterable in list/dict/etc constructions. + + .. describe:: str(x) + + Returns the emoji rendered for discord. + + Attributes + ----------- + name: :class:`str` + The name of the emoji. + id: :class:`int` + The emoji's ID. + require_colons: :class:`bool` + If colons are required to use this emoji in the client (:PJSalt: vs PJSalt). + animated: :class:`bool` + Whether an emoji is animated or not. + managed: :class:`bool` + If this emoji is managed by a Twitch integration. + guild_id: :class:`int` + The guild ID the emoji belongs to. + available: :class:`bool` + Whether the emoji is available for use. + user: Optional[:class:`User`] + The user that created the emoji. This can only be retrieved using :meth:`Guild.fetch_emoji` and + having the :attr:`~Permissions.manage_emojis` permission. + """ + + __slots__: Tuple[str, ...] = ( + 'require_colons', + 'animated', + 'managed', + 'id', + 'name', + '_roles', + 'guild_id', + '_state', + 'user', + 'available', + ) + + def __init__(self, *, guild: Guild, state: ConnectionState, data: EmojiPayload): + self.guild_id: int = guild.id + self._state: ConnectionState = state + self._from_data(data) + + def _from_data(self, emoji: EmojiPayload): + self.require_colons: bool = emoji.get('require_colons', False) + self.managed: bool = emoji.get('managed', False) + self.id: int = int(emoji['id']) # type: ignore + self.name: str = emoji['name'] # type: ignore + self.animated: bool = emoji.get('animated', False) + self.available: bool = emoji.get('available', True) + self._roles: SnowflakeList = SnowflakeList(map(int, emoji.get('roles', []))) + user = emoji.get('user') + self.user: Optional[User] = User(state=self._state, data=user) if user else None + + def _to_partial(self) -> PartialEmoji: + return PartialEmoji(name=self.name, animated=self.animated, id=self.id) + + def __iter__(self) -> Iterator[Tuple[str, Any]]: + for attr in self.__slots__: + if attr[0] != '_': + value = getattr(self, attr, None) + if value is not None: + yield (attr, value) + + def __str__(self) -> str: + if self.animated: + return f'' + return f'<:{self.name}:{self.id}>' + + def __repr__(self) -> str: + return f'' + + def __eq__(self, other: Any) -> bool: + return isinstance(other, _EmojiTag) and self.id == other.id + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return self.id >> 22 + + @property + def created_at(self) -> datetime: + """:class:`datetime.datetime`: Returns the emoji's creation time in UTC.""" + return snowflake_time(self.id) + + @property + def url(self) -> str: + """:class:`str`: Returns the URL of the emoji.""" + fmt = 'gif' if self.animated else 'png' + return f'{Asset.BASE}/emojis/{self.id}.{fmt}' + + @property + def roles(self) -> List[Role]: + """List[:class:`Role`]: A :class:`list` of roles that is allowed to use this emoji. + + If roles is empty, the emoji is unrestricted. + """ + guild = self.guild + if guild is None: + return [] + + return [role for role in guild.roles if self._roles.has(role.id)] + + @property + def guild(self) -> Guild: + """:class:`Guild`: The guild this emoji belongs to.""" + return self._state._get_guild(self.guild_id) + + def is_usable(self) -> bool: + """:class:`bool`: Whether the bot can use this emoji. + + .. versionadded:: 1.3 + """ + if not self.available: + return False + if not self._roles: + return True + emoji_roles, my_roles = self._roles, self.guild.me._roles + return any(my_roles.has(role_id) for role_id in emoji_roles) + + async def delete(self, *, reason: Optional[str] = None) -> None: + """|coro| + + Deletes the custom emoji. + + You must have :attr:`~Permissions.manage_emojis` permission to + do this. + + Parameters + ----------- + reason: Optional[:class:`str`] + The reason for deleting this emoji. Shows up on the audit log. + + Raises + ------- + Forbidden + You are not allowed to delete emojis. + HTTPException + An error occurred deleting the emoji. + """ + + 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) -> Emoji: + r"""|coro| + + Edits the custom emoji. + + You must have :attr:`~Permissions.manage_emojis` permission to + do this. + + .. versionchanged:: 2.0 + The newly updated emoji is returned. + + Parameters + ----------- + name: :class:`str` + The new emoji name. + roles: Optional[List[:class:`~discord.abc.Snowflake`]] + A list of roles that can use this emoji. An empty list can be passed to make it available to everyone. + reason: Optional[:class:`str`] + The reason for editing this emoji. Shows up on the audit log. + + Raises + ------- + Forbidden + You are not allowed to edit emojis. + HTTPException + An error occurred editing the emoji. + + Returns + -------- + :class:`Emoji` + The newly updated emoji. + """ + + payload = {} + if name is not MISSING: + payload['name'] = name + if roles is not MISSING: + payload['roles'] = [role.id for role in roles] + + 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) diff --git a/build/lib/discord/enums.py b/build/lib/discord/enums.py new file mode 100644 index 0000000000..71414d81a3 --- /dev/null +++ b/build/lib/discord/enums.py @@ -0,0 +1,719 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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. +""" + +import types +from collections import namedtuple +from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Type, TypeVar + +__all__ = ( + 'Enum', + 'ChannelType', + 'MessageType', + 'VoiceRegion', + 'SpeakingState', + 'VerificationLevel', + 'ContentFilter', + 'Status', + 'DefaultAvatar', + 'AuditLogAction', + 'AuditLogActionCategory', + 'UserFlags', + 'ActivityType', + 'NotificationLevel', + 'TeamMembershipState', + 'WebhookType', + 'ExpireBehaviour', + 'ExpireBehavior', + 'StickerType', + 'StickerFormatType', + 'InviteTarget', + 'VideoQualityMode', + 'ComponentType', + 'ButtonStyle', + 'StagePrivacyLevel', + 'InteractionType', + 'InteractionResponseType', + 'NSFWLevel', + 'EmbeddedActivity', +) + + +def _create_value_cls(name, comparable): + cls = namedtuple('_EnumValue_' + name, 'name value') + cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' + 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 + +def _is_descriptor(obj): + return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__') + + +class EnumMeta(type): + 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 = {} + member_mapping = {} + member_names = [] + + value_cls = _create_value_cls(name, comparable) + for key, value in list(attrs.items()): + is_descriptor = _is_descriptor(value) + if key[0] == '_' and not is_descriptor: + continue + + # Special case classmethod to just pass through + if isinstance(value, classmethod): + continue + + if is_descriptor: + setattr(value_cls, key, value) + del attrs[key] + continue + + try: + new_value = value_mapping[value] + except KeyError: + new_value = value_cls(name=key, value=value) + value_mapping[value] = new_value + member_names.append(key) + + member_mapping[key] = new_value + attrs[key] = new_value + + attrs['_enum_value_map_'] = value_mapping + attrs['_enum_member_map_'] = member_mapping + attrs['_enum_member_names_'] = member_names + attrs['_enum_value_cls_'] = value_cls + actual_cls = super().__new__(cls, name, bases, attrs) + value_cls._actual_enum_cls_ = actual_cls # type: ignore + return actual_cls + + def __iter__(cls): + return (cls._enum_member_map_[name] for name in cls._enum_member_names_) + + def __reversed__(cls): + return (cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_)) + + def __len__(cls): + return len(cls._enum_member_names_) + + def __repr__(cls): + return f'' + + @property + def __members__(cls): + return types.MappingProxyType(cls._enum_member_map_) + + def __call__(cls, value): + try: + return cls._enum_value_map_[value] + except (KeyError, TypeError): + raise ValueError(f"{value!r} is not a valid {cls.__name__}") + + def __getitem__(cls, key): + return cls._enum_member_map_[key] + + def __setattr__(cls, name, value): + raise TypeError('Enums are immutable.') + + def __delattr__(cls, attr): + raise TypeError('Enums are immutable') + + def __instancecheck__(self, instance): + # isinstance(x, Y) + # -> __instancecheck__(Y, x) + try: + return instance._actual_enum_cls_ is self + except AttributeError: + return False + + +if TYPE_CHECKING: + from enum import Enum +else: + + class Enum(metaclass=EnumMeta): + @classmethod + def try_value(cls, value): + try: + return cls._enum_value_map_[value] + except (KeyError, TypeError): + return value + + +class ChannelType(Enum): + text = 0 + private = 1 + voice = 2 + group = 3 + category = 4 + news = 5 + store = 6 + news_thread = 10 + public_thread = 11 + private_thread = 12 + stage_voice = 13 + directory = 14 + forum = 15 + + def __str__(self): + return self.name + + +class MessageType(Enum): + default = 0 + recipient_add = 1 + recipient_remove = 2 + call = 3 + channel_name_change = 4 + channel_icon_change = 5 + pins_add = 6 + new_member = 7 + premium_guild_subscription = 8 + premium_guild_tier_1 = 9 + premium_guild_tier_2 = 10 + premium_guild_tier_3 = 11 + channel_follow_add = 12 + guild_stream = 13 + guild_discovery_disqualified = 14 + guild_discovery_requalified = 15 + guild_discovery_grace_period_initial_warning = 16 + guild_discovery_grace_period_final_warning = 17 + thread_created = 18 + reply = 19 + application_command = 20 + thread_starter_message = 21 + guild_invite_reminder = 22 + context_menu_command = 23 + + +class VoiceRegion(Enum): + us_west = 'us-west' + us_east = 'us-east' + us_south = 'us-south' + us_central = 'us-central' + eu_west = 'eu-west' + eu_central = 'eu-central' + singapore = 'singapore' + london = 'london' + sydney = 'sydney' + amsterdam = 'amsterdam' + frankfurt = 'frankfurt' + brazil = 'brazil' + hongkong = 'hongkong' + russia = 'russia' + japan = 'japan' + southafrica = 'southafrica' + south_korea = 'south-korea' + india = 'india' + europe = 'europe' + dubai = 'dubai' + vip_us_east = 'vip-us-east' + vip_us_west = 'vip-us-west' + vip_amsterdam = 'vip-amsterdam' + + def __str__(self): + return self.value + + +class SpeakingState(Enum): + none = 0 + voice = 1 + soundshare = 2 + priority = 4 + + def __str__(self): + return self.name + + def __int__(self): + return self.value + + +class VerificationLevel(Enum, comparable=True): + none = 0 + low = 1 + medium = 2 + high = 3 + highest = 4 + + def __str__(self): + return self.name + + +class ContentFilter(Enum, comparable=True): + disabled = 0 + no_role = 1 + all_members = 2 + + def __str__(self): + return self.name + + +class Status(Enum): + online = 'online' + offline = 'offline' + idle = 'idle' + dnd = 'dnd' + do_not_disturb = 'dnd' + invisible = 'invisible' + streaming = 'streaming' + + def __str__(self): + return self.value + + +class DefaultAvatar(Enum): + blurple = 0 + grey = 1 + gray = 1 + green = 2 + orange = 3 + red = 4 + + def __str__(self): + return self.name + + +class NotificationLevel(Enum, comparable=True): + all_messages = 0 + only_mentions = 1 + + +class AuditLogActionCategory(Enum): + create = 1 + delete = 2 + update = 3 + + +class AuditLogAction(Enum): + # fmt: off + guild_update = 1 + channel_create = 10 + channel_update = 11 + channel_delete = 12 + overwrite_create = 13 + overwrite_update = 14 + overwrite_delete = 15 + kick = 20 + member_prune = 21 + ban = 22 + unban = 23 + member_update = 24 + member_role_update = 25 + member_move = 26 + member_disconnect = 27 + bot_add = 28 + role_create = 30 + role_update = 31 + role_delete = 32 + invite_create = 40 + invite_update = 41 + invite_delete = 42 + webhook_create = 50 + webhook_update = 51 + webhook_delete = 52 + emoji_create = 60 + emoji_update = 61 + emoji_delete = 62 + message_delete = 72 + message_bulk_delete = 73 + message_pin = 74 + message_unpin = 75 + integration_create = 80 + integration_update = 81 + integration_delete = 82 + stage_instance_create = 83 + stage_instance_update = 84 + stage_instance_delete = 85 + sticker_create = 90 + sticker_update = 91 + sticker_delete = 92 + scheduled_event_create = 100 + scheduled_event_update = 101 + scheduled_event_delete = 102 + thread_create = 110 + thread_update = 111 + thread_delete = 112 + # fmt: on + + @property + def category(self) -> Optional[AuditLogActionCategory]: + # fmt: off + lookup: Dict[AuditLogAction, Optional[AuditLogActionCategory]] = { + AuditLogAction.guild_update: AuditLogActionCategory.update, + AuditLogAction.channel_create: AuditLogActionCategory.create, + AuditLogAction.channel_update: AuditLogActionCategory.update, + AuditLogAction.channel_delete: AuditLogActionCategory.delete, + AuditLogAction.overwrite_create: AuditLogActionCategory.create, + AuditLogAction.overwrite_update: AuditLogActionCategory.update, + AuditLogAction.overwrite_delete: AuditLogActionCategory.delete, + AuditLogAction.kick: None, + AuditLogAction.member_prune: None, + AuditLogAction.ban: None, + AuditLogAction.unban: None, + AuditLogAction.member_update: AuditLogActionCategory.update, + AuditLogAction.member_role_update: AuditLogActionCategory.update, + AuditLogAction.member_move: None, + AuditLogAction.member_disconnect: None, + AuditLogAction.bot_add: None, + AuditLogAction.role_create: AuditLogActionCategory.create, + AuditLogAction.role_update: AuditLogActionCategory.update, + AuditLogAction.role_delete: AuditLogActionCategory.delete, + AuditLogAction.invite_create: AuditLogActionCategory.create, + AuditLogAction.invite_update: AuditLogActionCategory.update, + AuditLogAction.invite_delete: AuditLogActionCategory.delete, + AuditLogAction.webhook_create: AuditLogActionCategory.create, + AuditLogAction.webhook_update: AuditLogActionCategory.update, + AuditLogAction.webhook_delete: AuditLogActionCategory.delete, + AuditLogAction.emoji_create: AuditLogActionCategory.create, + AuditLogAction.emoji_update: AuditLogActionCategory.update, + AuditLogAction.emoji_delete: AuditLogActionCategory.delete, + AuditLogAction.message_delete: AuditLogActionCategory.delete, + AuditLogAction.message_bulk_delete: AuditLogActionCategory.delete, + AuditLogAction.message_pin: None, + AuditLogAction.message_unpin: None, + AuditLogAction.integration_create: AuditLogActionCategory.create, + AuditLogAction.integration_update: AuditLogActionCategory.update, + AuditLogAction.integration_delete: AuditLogActionCategory.delete, + AuditLogAction.stage_instance_create: AuditLogActionCategory.create, + AuditLogAction.stage_instance_update: AuditLogActionCategory.update, + AuditLogAction.stage_instance_delete: AuditLogActionCategory.delete, + AuditLogAction.sticker_create: AuditLogActionCategory.create, + AuditLogAction.sticker_update: AuditLogActionCategory.update, + AuditLogAction.sticker_delete: AuditLogActionCategory.delete, + AuditLogAction.scheduled_event_create: AuditLogActionCategory.create, + AuditLogAction.scheduled_event_update: AuditLogActionCategory.update, + AuditLogAction.scheduled_event_delete: AuditLogActionCategory.delete, + AuditLogAction.thread_create: AuditLogActionCategory.create, + AuditLogAction.thread_update: AuditLogActionCategory.update, + AuditLogAction.thread_delete: AuditLogActionCategory.delete, + } + # fmt: on + return lookup[self] + + @property + def target_type(self) -> Optional[str]: + v = self.value + if v == -1: + return 'all' + elif v < 10: + return 'guild' + elif v < 20: + return 'channel' + elif v < 30: + return 'user' + elif v < 40: + return 'role' + elif v < 50: + return 'invite' + elif v < 60: + return 'webhook' + elif v < 70: + return 'emoji' + elif v == 73: + return 'channel' + elif v < 80: + return 'message' + elif v < 83: + return 'integration' + elif v < 90: + return 'stage_instance' + elif v < 93: + return 'sticker' + elif v < 103: + return 'scheduled_event' + elif v < 113: + return 'thread' + + +class UserFlags(Enum): + staff = 1 + partner = 2 + hypesquad = 4 + bug_hunter = 8 + mfa_sms = 16 + premium_promo_dismissed = 32 + hypesquad_bravery = 64 + hypesquad_brilliance = 128 + hypesquad_balance = 256 + early_supporter = 512 + team_user = 1024 + partner_or_verification_application = 2048 + system = 4096 + has_unread_urgent_messages = 8192 + bug_hunter_level_2 = 16384 + underage_deleted = 32768 + verified_bot = 65536 + verified_bot_developer = 131072 + discord_certified_moderator = 262144 + bot_http_interactions = 524288 + spammer = 1048576 + + +class ActivityType(Enum): + unknown = -1 + playing = 0 + streaming = 1 + listening = 2 + watching = 3 + custom = 4 + competing = 5 + + def __int__(self): + return self.value + + +class TeamMembershipState(Enum): + invited = 1 + accepted = 2 + + +class WebhookType(Enum): + incoming = 1 + channel_follower = 2 + application = 3 + + +class ExpireBehaviour(Enum): + remove_role = 0 + kick = 1 + + +ExpireBehavior = ExpireBehaviour + + +class StickerType(Enum): + standard = 1 + guild = 2 + + +class StickerFormatType(Enum): + png = 1 + apng = 2 + 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): + unknown = 0 + stream = 1 + embedded_application = 2 + + +class InteractionType(Enum): + ping = 1 + application_command = 2 + component = 3 + auto_complete = 4 + + +class InteractionResponseType(Enum): + pong = 1 + # ack = 2 (deprecated) + # channel_message = 3 (deprecated) + channel_message = 4 # (with source) + deferred_channel_message = 5 # (with source) + deferred_message_update = 6 # for components + message_update = 7 # for components + auto_complete_result = 8 # for autocomplete interactions + + +class VideoQualityMode(Enum): + auto = 1 + full = 2 + + def __int__(self): + return self.value + + +class ComponentType(Enum): + action_row = 1 + button = 2 + select = 3 + + def __int__(self): + return self.value + + +class ButtonStyle(Enum): + primary = 1 + secondary = 2 + success = 3 + danger = 4 + link = 5 + + # Aliases + blurple = 1 + grey = 2 + gray = 2 + green = 3 + red = 4 + url = 5 + + def __int__(self): + return self.value + + +class ApplicationType(Enum): + game = 1 + music = 2 + ticketed_events = 3 + guild_role_subscriptions = 4 + + +class StagePrivacyLevel(Enum): + public = 1 + closed = 2 + guild_only = 2 + + +class NSFWLevel(Enum, comparable=True): + default = 0 + explicit = 1 + safe = 2 + age_restricted = 3 + + +class SlashCommandOptionType(Enum): + sub_command = 1 + sub_command_group = 2 + string = 3 + integer = 4 + boolean = 5 + user = 6 + channel = 7 + role = 8 + mentionable = 9 + number = 10 + attachment = 11 + + @classmethod + def from_datatype(cls, datatype): + if isinstance(datatype, tuple): # typing.Union has been used + datatypes = [cls.from_datatype(op) for op in datatype] + if all([x == cls.channel for x in datatypes]): + return cls.channel + elif set(datatypes) <= {cls.role, cls.user}: + return cls.mentionable + else: + raise TypeError('Invalid usage of typing.Union') + + if issubclass(datatype, str): + return cls.string + if issubclass(datatype, bool): + return cls.boolean + if issubclass(datatype, int): + return cls.integer + if issubclass(datatype, float): + return cls.number + + if datatype.__name__ in ["Member", "User"]: + return cls.user + if datatype.__name__ in [ + "GuildChannel", "TextChannel", + "VoiceChannel", "StageChannel", + "CategoryChannel" + ]: + return cls.channel + if datatype.__name__ == "Role": + return cls.role + if datatype.__name__ == "Mentionable": + return cls.mentionable + + # TODO: Improve the error message + raise TypeError(f'Invalid class {datatype} used as an input type for an Option') + + +class EmbeddedActivity(Enum): + awkword = 879863881349087252 + betrayal = 773336526917861400 + checkers_in_the_park = 832013003968348200 + checkers_in_the_park_dev = 832012682520428625 + checkers_in_the_park_staging = 832012938398400562 + checkers_in_the_park_qa = 832012894068801636 + chess_in_the_park = 832012774040141894 + chess_in_the_park_dev = 832012586023256104 + chest_in_the_park_staging = 832012730599735326 + chest_in_the_park_qa = 832012815819604009 + decoders_dev = 891001866073296967 + doodle_crew = 878067389634314250 + doodle_crew_dev = 878067427668275241 + fishington = 814288819477020702 + letter_tile = 879863686565621790 + ocho = 832025144389533716 + ocho_dev = 832013108234289153 + ocho_staging = 832025061657280566 + ocho_qa = 832025114077298718 + poker_night = 755827207812677713 + poker_night_staging = 763116274876022855 + poker_night_qa = 801133024841957428 + putts = 832012854282158180 + sketchy_artist = 879864070101172255 + sketchy_artist_dev = 879864104980979792 + spell_cast = 852509694341283871 + watch_together = 880218394199220334 + watch_together_dev = 880218832743055411 + word_snacks = 879863976006127627 + word_snacks_dev = 879864010126786570 + youtube_together = 755600276941176913 + +T = TypeVar('T') + +def create_unknown_value(cls: Type[T], val: Any) -> T: + value_cls = cls._enum_value_cls_ # type: ignore + name = f'unknown_{val}' + return value_cls(name=name, value=val) + + +def try_enum(cls: Type[T], val: Any) -> T: + """A function that tries to turn the value into enum ``cls``. + + If it fails it returns a proxy invalid value instead. + """ + + try: + return cls._enum_value_map_[val] # type: ignore + except (KeyError, TypeError, AttributeError): + return create_unknown_value(cls, val) diff --git a/build/lib/discord/errors.py b/build/lib/discord/errors.py new file mode 100644 index 0000000000..6cc549c61d --- /dev/null +++ b/build/lib/discord/errors.py @@ -0,0 +1,365 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 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__ = ( + 'DiscordException', + 'ClientException', + 'NoMoreItems', + 'GatewayNotFound', + 'ValidationError', + 'HTTPException', + 'Forbidden', + 'NotFound', + 'DiscordServerError', + 'InvalidData', + 'InvalidArgument', + 'LoginFailure', + 'ConnectionClosed', + 'PrivilegedIntentsRequired', + 'InteractionResponded', + 'ExtensionError', + 'ExtensionAlreadyLoaded', + 'ExtensionNotLoaded', + 'NoEntryPointError', + 'ExtensionFailed', + 'ExtensionNotFound' +) + + +class DiscordException(Exception): + """Base exception class for pycord + + Ideally speaking, this could be caught to handle any exceptions raised from this library. + """ + + pass + + +class ClientException(DiscordException): + """Exception that's raised when an operation in the :class:`Client` fails. + + These are usually for exceptions that happened due to user input. + """ + + pass + + +class NoMoreItems(DiscordException): + """Exception that is raised when an async iteration operation has no more items.""" + + pass + + +class GatewayNotFound(DiscordException): + """An exception that is raised when the gateway for Discord could not be found""" + + def __init__(self): + message = 'The gateway to connect to discord was not found.' + super().__init__(message) + +class ValidationError(DiscordException): + """An Exception that is raised when there is a Validation Error.""" + + pass + +def _flatten_error_dict(d: Dict[str, Any], key: str = '') -> Dict[str, str]: + items: List[Tuple[str, str]] = [] + for k, v in d.items(): + new_key = key + '.' + k if key else k + + if isinstance(v, dict): + try: + _errors: List[Dict[str, Any]] = v['_errors'] + except KeyError: + items.extend(_flatten_error_dict(v, new_key).items()) + else: + items.append((new_key, ' '.join(x.get('message', '') for x in _errors))) + else: + items.append((new_key, v)) + + return dict(items) + + +class HTTPException(DiscordException): + """Exception that's raised when an HTTP request operation fails. + + Attributes + ------------ + response: :class:`aiohttp.ClientResponse` + The response of the failed HTTP request. This is an + instance of :class:`aiohttp.ClientResponse`. In some cases + this could also be a :class:`requests.Response`. + + text: :class:`str` + The text of the error. Could be an empty string. + status: :class:`int` + The status code of the HTTP request. + code: :class:`int` + The Discord specific error code for the failure. + """ + + def __init__(self, response: _ResponseType, message: Optional[Union[str, Dict[str, Any]]]): + self.response: _ResponseType = response + self.status: int = response.status # type: ignore + self.code: int + self.text: str + if isinstance(message, dict): + self.code = message.get('code', 0) + base = message.get('message', '') + errors = message.get('errors') + if errors: + errors = _flatten_error_dict(errors) + helpful = '\n'.join('In %s: %s' % t for t in errors.items()) + self.text = base + '\n' + helpful + else: + self.text = base + else: + self.text = message or '' + self.code = 0 + + fmt = '{0.status} {0.reason} (error code: {1})' + if len(self.text): + fmt += ': {2}' + + super().__init__(fmt.format(self.response, self.code, self.text)) + + +class Forbidden(HTTPException): + """Exception that's raised for when status code 403 occurs. + + Subclass of :exc:`HTTPException` + """ + + pass + + +class NotFound(HTTPException): + """Exception that's raised for when status code 404 occurs. + + Subclass of :exc:`HTTPException` + """ + + pass + + +class DiscordServerError(HTTPException): + """Exception that's raised for when a 500 range status code occurs. + + Subclass of :exc:`HTTPException`. + + .. versionadded:: 1.5 + """ + + pass + + +class InvalidData(ClientException): + """Exception that's raised when the library encounters unknown + or invalid data from Discord. + """ + + pass + + +class InvalidArgument(ClientException): + """Exception that's raised when an argument to a function + is invalid some way (e.g. wrong value or wrong type). + + This could be considered the analogous of ``ValueError`` and + ``TypeError`` except inherited from :exc:`ClientException` and thus + :exc:`DiscordException`. + """ + + pass + + +class LoginFailure(ClientException): + """Exception that's raised when the :meth:`Client.login` function + fails to log you in from improper credentials or some other misc. + failure. + """ + + pass + + +class ConnectionClosed(ClientException): + """Exception that's raised when the gateway connection is + closed for reasons that could not be handled internally. + + Attributes + ----------- + code: :class:`int` + The close code of the websocket. + reason: :class:`str` + The reason provided for the closure. + shard_id: Optional[:class:`int`] + The shard ID that got closed if applicable. + """ + + def __init__(self, socket: ClientWebSocketResponse, *, shard_id: Optional[int], code: Optional[int] = None): + # This exception is just the same exception except + # reconfigured to subclass ClientException for users + self.code: int = code or socket.close_code or -1 + # aiohttp doesn't seem to consistently provide close reason + self.reason: str = '' + self.shard_id: Optional[int] = shard_id + super().__init__(f'Shard ID {self.shard_id} WebSocket closed with {self.code}') + + +class PrivilegedIntentsRequired(ClientException): + """Exception that's raised when the gateway is requesting privileged intents + but they're not ticked in the developer page yet. + + Go to https://discord.com/developers/applications/ and enable the intents + that are required. Currently these are as follows: + + - :attr:`Intents.members` + - :attr:`Intents.presences` + + Attributes + ----------- + shard_id: Optional[:class:`int`] + The shard ID that got closed if applicable. + """ + + def __init__(self, shard_id: Optional[int]): + self.shard_id: Optional[int] = shard_id + msg = ( + 'Shard ID %s is requesting privileged intents that have not been explicitly enabled in the ' + '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.' + ) + 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') + +class ExtensionError(DiscordException): + """Base exception for extension related errors. + + This inherits from :exc:`~discord.DiscordException`. + + Attributes + ------------ + name: :class:`str` + The extension that had an error. + """ + def __init__(self, message: Optional[str] = None, *args: Any, name: str) -> None: + self.name: str = name + message = message or f'Extension {name!r} had an error.' + # clean-up @everyone and @here mentions + m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere') + super().__init__(m, *args) + +class ExtensionAlreadyLoaded(ExtensionError): + """An exception raised when an extension has already been loaded. + + This inherits from :exc:`ExtensionError` + """ + def __init__(self, name: str) -> None: + super().__init__(f'Extension {name!r} is already loaded.', name=name) + +class ExtensionNotLoaded(ExtensionError): + """An exception raised when an extension was not loaded. + + This inherits from :exc:`ExtensionError` + """ + def __init__(self, name: str) -> None: + super().__init__(f'Extension {name!r} has not been loaded.', name=name) + +class NoEntryPointError(ExtensionError): + """An exception raised when an extension does not have a ``setup`` entry point function. + + This inherits from :exc:`ExtensionError` + """ + def __init__(self, name: str) -> None: + super().__init__(f"Extension {name!r} has no 'setup' function.", name=name) + +class ExtensionFailed(ExtensionError): + """An exception raised when an extension failed to load during execution of the module or ``setup`` entry point. + + This inherits from :exc:`ExtensionError` + + Attributes + ----------- + name: :class:`str` + The extension that had the error. + original: :exc:`Exception` + The original exception that was raised. You can also get this via + the ``__cause__`` attribute. + """ + def __init__(self, name: str, original: Exception) -> None: + self.original: Exception = original + msg = f'Extension {name!r} raised an error: {original.__class__.__name__}: {original}' + super().__init__(msg, name=name) + +class ExtensionNotFound(ExtensionError): + """An exception raised when an extension is not found. + + This inherits from :exc:`ExtensionError` + + .. versionchanged:: 1.3 + Made the ``original`` attribute always None. + + Attributes + ----------- + name: :class:`str` + The extension that had the error. + """ + def __init__(self, name: str) -> None: + msg = f'Extension {name!r} could not be found.' + super().__init__(msg, name=name) diff --git a/build/lib/discord/ext/commands/__init__.py b/build/lib/discord/ext/commands/__init__.py new file mode 100644 index 0000000000..5366b03406 --- /dev/null +++ b/build/lib/discord/ext/commands/__init__.py @@ -0,0 +1,19 @@ +""" +discord.ext.commands +~~~~~~~~~~~~~~~~~~~~~ + +An extension module to facilitate creation of bot commands. + +:copyright: (c) 2015-2021 Rapptz & (c) 2021-present Pycord Development +:license: MIT, see LICENSE for more details. +""" + +from .bot import * +from .context import * +from .core import * +from .errors import * +from .help import * +from .converter import * +from .cooldowns import * +from .cog import * +from .flags import * \ No newline at end of file diff --git a/build/lib/discord/ext/commands/_types.py b/build/lib/discord/ext/commands/_types.py new file mode 100644 index 0000000000..cb545aed9c --- /dev/null +++ b/build/lib/discord/ext/commands/_types.py @@ -0,0 +1,43 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 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]]] diff --git a/build/lib/discord/ext/commands/bot.py b/build/lib/discord/ext/commands/bot.py new file mode 100644 index 0000000000..c24223c1e3 --- /dev/null +++ b/build/lib/discord/ext/commands/bot.py @@ -0,0 +1,987 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + + +import asyncio +import collections +import collections.abc +import inspect +import importlib.util +import sys +import traceback +import types +from typing import Any, Callable, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union + +import discord + +from .core import GroupMixin +from .view import StringView +from .context import Context +from . import errors +from .help import HelpCommand, DefaultHelpCommand +from .cog import Cog + +if TYPE_CHECKING: + import importlib.machinery + + from discord.message import Message + from ._types import ( + Check, + CoroFunc, + ) + +__all__ = ( + 'when_mentioned', + 'when_mentioned_or', + 'Bot', + 'AutoShardedBot', +) + +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. + + These are meant to be passed into the :attr:`.Bot.command_prefix` attribute. + """ + # 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: str) -> Callable[[Union[Bot, AutoShardedBot], Message], List[str]]: + """A callable that implements when mentioned or other prefixes provided. + + These are meant to be passed into the :attr:`.Bot.command_prefix` attribute. + + Example + -------- + + .. code-block:: python3 + + bot = commands.Bot(command_prefix=commands.when_mentioned_or('!')) + + + .. note:: + + This callable returns another callable, so if this is done inside a custom + callable, you must call the returned callable, for example: + + .. code-block:: python3 + + async def get_prefix(bot, message): + extras = await prefixes_for(message.guild) # returns a list + return commands.when_mentioned_or(*extras)(bot, message) + + + See Also + ---------- + :func:`.when_mentioned` + """ + def inner(bot, msg): + r = list(prefixes) + r = when_mentioned(bot, msg) + r + return r + + return inner + +def _is_submodule(parent: str, child: str) -> bool: + return parent == child or child.startswith(parent + ".") + +class _DefaultRepr: + def __repr__(self): + return '' + +_default = _DefaultRepr() + +class BotBase(GroupMixin): + _supports_prefixed_commands = True + def __init__(self, command_prefix=when_mentioned, help_command=_default, **options): + super().__init__(**options) + self.command_prefix = command_prefix + self._help_command = None + self.strip_after_prefix = options.get('strip_after_prefix', False) + + if help_command is _default: + self.help_command = DefaultHelpCommand() + else: + self.help_command = help_command + + @discord.utils.copy_doc(discord.Client.close) + async def close(self) -> None: + for extension in tuple(self.__extensions): + try: + self.unload_extension(extension) + except Exception: + pass + + for cog in tuple(self.__cogs): + try: + self.remove_cog(cog) + except Exception: + pass + + await super().close() # type: ignore + + async def on_command_error(self, context: Context, exception: errors.CommandError) -> None: + """|coro| + + The default command error handler provided by the bot. + + By default this prints to :data:`sys.stderr` however it could be + overridden to have a different implementation. + + This only fires if you do not specify any listeners for command error. + """ + if self.extra_events.get('on_command_error', None): + return + + command = context.command + if command and command.has_error_handler(): + return + + cog = context.cog + if cog and cog.has_error_handler(): + return + + print(f'Ignoring exception in command {context.command}:', file=sys.stderr) + traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr) + + # global check registration + + def check(self, func: T) -> T: + r"""A decorator that adds a global check to the bot. + + A global check is similar to a :func:`.check` that is applied + on a per command basis except it is run before any command checks + have been verified and applies to every command the bot has. + + .. note:: + + This function can either be a regular function or a coroutine. + + Similar to a command :func:`.check`\, this takes a single parameter + of type :class:`.Context` and can only raise exceptions inherited from + :exc:`.CommandError`. + + Example + --------- + + .. code-block:: python3 + + @bot.check + def check_commands(ctx): + return ctx.command.qualified_name in allowed_commands + + """ + # T was used instead of Check to ensure the type matches on return + self.add_check(func) # type: ignore + return func + + def add_check(self, func: Check, *, call_once: bool = False) -> None: + """Adds a global check to the bot. + + This is the non-decorator interface to :meth:`.check` + and :meth:`.check_once`. + + Parameters + ----------- + func + The function that was used as a global check. + call_once: :class:`bool` + If the function should only be called once per + :meth:`.invoke` call. + """ + + if call_once: + self._check_once.append(func) + else: + self._checks.append(func) + + def remove_check(self, func: Check, *, call_once: bool = False) -> None: + """Removes a global check from the bot. + + This function is idempotent and will not raise an exception + if the function is not in the global checks. + + Parameters + ----------- + func + The function to remove from the global checks. + call_once: :class:`bool` + If the function was added with ``call_once=True`` in + the :meth:`.Bot.add_check` call or using :meth:`.check_once`. + """ + l = self._check_once if call_once else self._checks + + try: + l.remove(func) + except ValueError: + pass + + def check_once(self, func: CFT) -> CFT: + r"""A decorator that adds a "call once" global check to the bot. + + Unlike regular global checks, this one is called only once + per :meth:`.invoke` call. + + Regular global checks are called whenever a command is called + or :meth:`.Command.can_run` is called. This type of check + bypasses that and ensures that it's called only once, even inside + the default help command. + + .. note:: + + When using this function the :class:`.Context` sent to a group subcommand + may only parse the parent command and not the subcommands due to it + being invoked once per :meth:`.Bot.invoke` call. + + .. note:: + + This function can either be a regular function or a coroutine. + + Similar to a command :func:`.check`\, this takes a single parameter + of type :class:`.Context` and can only raise exceptions inherited from + :exc:`.CommandError`. + + Example + --------- + + .. code-block:: python3 + + @bot.check_once + def whitelist(ctx): + return ctx.message.author.id in my_whitelist + + """ + self.add_check(func, call_once=True) + return func + + async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool: + data = self._check_once if call_once else self._checks + + if len(data) == 0: + return True + + # 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: discord.User) -> bool: + """|coro| + + Checks if a :class:`~discord.User` or :class:`~discord.Member` is the owner of + this bot. + + If an :attr:`owner_id` is not set, it is fetched automatically + through the use of :meth:`~.Bot.application_info`. + + .. versionchanged:: 1.3 + The function also checks if the application is team-owned if + :attr:`owner_ids` is not set. + + Parameters + ----------- + user: :class:`.abc.User` + The user to check for. + + Returns + -------- + :class:`bool` + Whether the user is the owner. + """ + + if self.owner_id: + return user.id == self.owner_id + elif self.owner_ids: + return user.id in self.owner_ids + else: + + app = await self.application_info() # type: ignore + if app.team: + self.owner_ids = ids = {m.id for m in app.team.members} + return user.id in ids + else: + self.owner_id = owner_id = app.owner.id + return user.id == owner_id + + def before_invoke(self, coro: CFT) -> CFT: + """A decorator that registers a coroutine as a pre-invoke hook. + + A pre-invoke hook is called directly before the command is + called. This makes it a useful function to set up database + connections or any type of set up required. + + This pre-invoke hook takes a sole parameter, a :class:`.Context`. + + .. note:: + + The :meth:`~.Bot.before_invoke` and :meth:`~.Bot.after_invoke` hooks are + only called if all checks and argument parsing procedures pass + without error. If any check or argument parsing procedures fail + then the hooks are not called. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the pre-invoke hook. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The pre-invoke hook must be a coroutine.') + + self._before_invoke = coro + return coro + + def after_invoke(self, coro: CFT) -> CFT: + r"""A decorator that registers a coroutine as a post-invoke hook. + + A post-invoke hook is called directly after the command is + called. This makes it a useful function to clean-up database + connections or any type of clean up required. + + This post-invoke hook takes a sole parameter, a :class:`.Context`. + + .. note:: + + Similar to :meth:`~.Bot.before_invoke`\, this is not called unless + checks and argument parsing procedures succeed. This hook is, + however, **always** called regardless of the internal command + callback raising an error (i.e. :exc:`.CommandInvokeError`\). + This makes it ideal for clean-up scenarios. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the post-invoke hook. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The post-invoke hook must be a coroutine.') + + self._after_invoke = coro + return coro + + + # cogs + + def add_cog(self, cog: Cog, *, override: bool = False) -> None: + """Adds a "cog" to the bot. + + A cog is a class that has its own event listeners and commands. + + .. versionchanged:: 2.0 + + :exc:`.ClientException` is raised when a cog with the same name + is already loaded. + + Parameters + ----------- + cog: :class:`.Cog` + The cog to register to the bot. + override: :class:`bool` + If a previously loaded cog with the same name should be ejected + instead of raising an error. + + .. versionadded:: 2.0 + + Raises + ------- + TypeError + The cog does not inherit from :class:`.Cog`. + CommandError + An error happened during loading. + .ClientException + A cog with the same name is already loaded. + """ + + if not isinstance(cog, Cog): + raise TypeError('cogs must derive from Cog') + + cog_name = cog.__cog_name__ + existing = self.__cogs.get(cog_name) + + if existing is not None: + if not override: + raise discord.ClientException(f'Cog named {cog_name!r} already loaded') + self.remove_cog(cog_name) + + cog = cog._inject(self) + self.__cogs[cog_name] = cog + + def get_cog(self, name: str) -> Optional[Cog]: + """Gets the cog instance requested. + + If the cog is not found, ``None`` is returned instead. + + Parameters + ----------- + name: :class:`str` + The name of the cog you are requesting. + This is equivalent to the name passed via keyword + argument in class creation or the class name if unspecified. + + Returns + -------- + Optional[:class:`Cog`] + The cog that was requested. If not found, returns ``None``. + """ + return self.__cogs.get(name) + + def remove_cog(self, name: str) -> Optional[Cog]: + """Removes a cog from the bot and returns it. + + All registered commands and event listeners that the + cog has registered will be removed as well. + + If no cog is found then this method has no effect. + + Parameters + ----------- + name: :class:`str` + 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) + if cog is None: + return + + help_command = self._help_command + if help_command and help_command.cog is cog: + help_command.cog = None + cog._eject(self) + + return cog + + @property + def cogs(self) -> Mapping[str, Cog]: + """Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog.""" + return types.MappingProxyType(self.__cogs) + + # extensions + + def _remove_module_references(self, name: str) -> None: + # find all references to the module + # remove the cogs registered from the module + for cogname, cog in self.__cogs.copy().items(): + if _is_submodule(name, cog.__module__): + self.remove_cog(cogname) + + # remove all the commands from the module + for cmd in self.all_commands.copy().values(): + if cmd.module is not None and _is_submodule(name, cmd.module): + if isinstance(cmd, GroupMixin): + cmd.recursively_remove_all_commands() + self.remove_command(cmd.name) + + # remove all the listeners from the module + for event_list in self.extra_events.copy().values(): + remove = [] + for index, event in enumerate(event_list): + if event.__module__ is not None and _is_submodule(name, event.__module__): + remove.append(index) + + for index in reversed(remove): + del event_list[index] + + def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None: + try: + func = getattr(lib, 'teardown') + except AttributeError: + pass + else: + try: + func(self) + except Exception: + pass + finally: + self.__extensions.pop(key, None) + sys.modules.pop(key, None) + name = lib.__name__ + for module in list(sys.modules.keys()): + if _is_submodule(name, module): + del sys.modules[module] + + def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None: + # precondition: key not in self.__extensions + lib = importlib.util.module_from_spec(spec) + sys.modules[key] = lib + try: + spec.loader.exec_module(lib) # type: ignore + except Exception as e: + del sys.modules[key] + raise discord.ExtensionFailed(key, e) from e + + try: + setup = getattr(lib, 'setup') + except AttributeError: + del sys.modules[key] + raise discord.NoEntryPointError(key) + + try: + setup(self) + except Exception as e: + del sys.modules[key] + self._remove_module_references(lib.__name__) + self._call_module_finalizers(lib, key) + raise discord.ExtensionFailed(key, e) from e + else: + self.__extensions[key] = lib + + def _resolve_name(self, name: str, package: Optional[str]) -> str: + try: + return importlib.util.resolve_name(name, package) + except ImportError: + raise discord.ExtensionNotFound(name) + + def load_extension(self, name: str, *, package: Optional[str] = None) -> None: + """Loads an extension. + + An extension is a python module that contains commands, cogs, or + listeners. + + An extension must have a global function, ``setup`` defined as + the entry point on what to do when the extension is loaded. This entry + point must have a single argument, the ``bot``. + + Parameters + ------------ + name: :class:`str` + The extension name to load. It must be dot separated like + regular Python imports if accessing a sub-module. e.g. + ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when loading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 + + Raises + -------- + ExtensionNotFound + The extension could not be imported. + This is also raised if the name of the extension could not + be resolved using the provided ``package`` parameter. + ExtensionAlreadyLoaded + The extension is already loaded. + NoEntryPointError + The extension does not have a setup function. + ExtensionFailed + The extension or its setup function had an execution error. + """ + + name = self._resolve_name(name, package) + if name in self.__extensions: + raise discord.ExtensionAlreadyLoaded(name) + + spec = importlib.util.find_spec(name) + if spec is None: + raise discord.ExtensionNotFound(name) + + self._load_from_module_spec(spec, name) + + def unload_extension(self, name: str, *, package: Optional[str] = None) -> None: + """Unloads an extension. + + When the extension is unloaded, all commands, listeners, and cogs are + removed from the bot and the module is un-imported. + + The extension can provide an optional global function, ``teardown``, + to do miscellaneous clean-up if necessary. This function takes a single + parameter, the ``bot``, similar to ``setup`` from + :meth:`~.Bot.load_extension`. + + Parameters + ------------ + name: :class:`str` + The extension name to unload. It must be dot separated like + regular Python imports if accessing a sub-module. e.g. + ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when unloading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 + + Raises + ------- + ExtensionNotFound + The name of the extension could not + be resolved using the provided ``package`` parameter. + ExtensionNotLoaded + The extension was not loaded. + """ + + name = self._resolve_name(name, package) + lib = self.__extensions.get(name) + if lib is None: + raise discord.ExtensionNotLoaded(name) + + self._remove_module_references(lib.__name__) + self._call_module_finalizers(lib, name) + + def reload_extension(self, name: str, *, package: Optional[str] = None) -> None: + """Atomically reloads an extension. + + This replaces the extension with the same extension, only refreshed. This is + equivalent to a :meth:`unload_extension` followed by a :meth:`load_extension` + except done in an atomic way. That is, if an operation fails mid-reload then + the bot will roll-back to the prior working state. + + Parameters + ------------ + name: :class:`str` + The extension name to reload. It must be dot separated like + regular Python imports if accessing a sub-module. e.g. + ``foo.test`` if you want to import ``foo/test.py``. + package: Optional[:class:`str`] + The package name to resolve relative imports with. + This is required when reloading an extension using a relative path, e.g ``.foo.test``. + Defaults to ``None``. + + .. versionadded:: 1.7 + + Raises + ------- + ExtensionNotLoaded + The extension was not loaded. + ExtensionNotFound + The extension could not be imported. + This is also raised if the name of the extension could not + be resolved using the provided ``package`` parameter. + NoEntryPointError + The extension does not have a setup function. + ExtensionFailed + The extension setup function had an execution error. + """ + + name = self._resolve_name(name, package) + lib = self.__extensions.get(name) + if lib is None: + raise discord.ExtensionNotLoaded(name) + + # get the previous module states from sys modules + modules = { + name: module + for name, module in sys.modules.items() + if _is_submodule(lib.__name__, name) + } + + try: + # Unload and then load the module... + self._remove_module_references(lib.__name__) + self._call_module_finalizers(lib, name) + self.load_extension(name) + except Exception: + # if the load failed, the remnants should have been + # cleaned from the load_extension function call + # so let's load it from our old compiled library. + lib.setup(self) # type: ignore + self.__extensions[name] = lib + + # revert sys.modules back to normal and raise back to caller + sys.modules.update(modules) + raise + + @property + def extensions(self) -> Mapping[str, types.ModuleType]: + """Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension.""" + return types.MappingProxyType(self.__extensions) + + # help command stuff + + @property + def help_command(self) -> Optional[HelpCommand]: + return self._help_command + + @help_command.setter + def help_command(self, value: Optional[HelpCommand]) -> None: + if value is not None: + if not isinstance(value, HelpCommand): + raise TypeError('help_command must be a subclass of HelpCommand') + if self._help_command is not None: + self._help_command._remove_from_bot(self) + self._help_command = value + value._add_to_bot(self) + elif self._help_command is not None: + self._help_command._remove_from_bot(self) + self._help_command = None + else: + self._help_command = None + + # command processing + + async def get_prefix(self, message: Message) -> Union[List[str], str]: + """|coro| + + Retrieves the prefix the bot is listening to + with the message as a context. + + Parameters + ----------- + message: :class:`discord.Message` + The message context to get the prefix of. + + Returns + -------- + Union[List[:class:`str`], :class:`str`] + A list of prefixes or a single prefix that the bot is + listening for. + """ + prefix = ret = self.command_prefix + if callable(prefix): + ret = await discord.utils.maybe_coroutine(prefix, self, message) + + if not isinstance(ret, str): + try: + ret = list(ret) + except TypeError: + # It's possible that a generator raised this exception. Don't + # replace it with our own error if that's the case. + if isinstance(ret, collections.abc.Iterable): + raise + + raise TypeError("command_prefix must be plain string, iterable of strings, or callable " + f"returning either of these, not {ret.__class__.__name__}") + + if not ret: + raise ValueError("Iterable command_prefix must contain at least one prefix") + + return ret + + async def get_context(self, message: Message, *, cls: Type[CXT] = Context) -> CXT: + r"""|coro| + + Returns the invocation context from the message. + + This is a more low-level counter-part for :meth:`.process_commands` + to allow users more fine grained control over the processing. + + The returned context is not guaranteed to be a valid invocation + context, :attr:`.Context.valid` must be checked to make sure it is. + If the context is not valid then it is not a valid candidate to be + invoked under :meth:`~.Bot.invoke`. + + Parameters + ----------- + message: :class:`discord.Message` + The message to get the invocation context from. + cls + The factory class that will be used to create the context. + By default, this is :class:`.Context`. Should a custom + class be provided, it must be similar enough to :class:`.Context`\'s + interface. + + Returns + -------- + :class:`.Context` + The invocation context. The type of this can change via the + ``cls`` parameter. + """ + + view = StringView(message.content) + ctx = cls(prefix=None, view=view, bot=self, message=message) + + if message.author.id == self.user.id: # type: ignore + return ctx + + prefix = await self.get_prefix(message) + invoked_prefix = prefix + + if isinstance(prefix, str): + if not view.skip_string(prefix): + return ctx + else: + try: + # if the context class' __init__ consumes something from the view this + # will be wrong. That seems unreasonable though. + if message.content.startswith(tuple(prefix)): + invoked_prefix = discord.utils.find(view.skip_string, prefix) + else: + return ctx + + except TypeError: + if not isinstance(prefix, list): + raise TypeError("get_prefix must return either a string or a list of string, " + f"not {prefix.__class__.__name__}") + + # It's possible a bad command_prefix got us here. + for value in prefix: + if not isinstance(value, str): + raise TypeError("Iterable command_prefix or list returned from get_prefix must " + f"contain only strings, not {value.__class__.__name__}") + + # Getting here shouldn't happen + raise + + if self.strip_after_prefix: + view.skip_ws() + + invoker = view.get_word() + ctx.invoked_with = invoker + # type-checker fails to narrow invoked_prefix type. + ctx.prefix = invoked_prefix # type: ignore + ctx.command = self.all_commands.get(invoker) + return ctx + + async def invoke(self, ctx: Context) -> None: + """|coro| + + Invokes the command given under the invocation context and + handles all the internal event dispatch mechanisms. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context to invoke. + """ + if ctx.command is not None: + self.dispatch('command', ctx) + try: + if await self.can_run(ctx, call_once=True): + await ctx.command.invoke(ctx) + else: + raise errors.CheckFailure('The global check once functions failed.') + except errors.CommandError as exc: + await ctx.command.dispatch_error(ctx, exc) + else: + self.dispatch('command_completion', ctx) + elif ctx.invoked_with: + exc = errors.CommandNotFound(f'Command "{ctx.invoked_with}" is not found') + self.dispatch('command_error', ctx, exc) + + async def process_commands(self, message: Message) -> None: + """|coro| + + This function processes the commands that have been registered + to the bot and other groups. Without this coroutine, none of the + commands will be triggered. + + By default, this coroutine is called inside the :func:`.on_message` + event. If you choose to override the :func:`.on_message` event, then + you should invoke this coroutine as well. + + This is built using other low level tools, and is equivalent to a + call to :meth:`~.Bot.get_context` followed by a call to :meth:`~.Bot.invoke`. + + This also checks if the message's author is a bot and doesn't + call :meth:`~.Bot.get_context` or :meth:`~.Bot.invoke` if so. + + Parameters + ----------- + message: :class:`discord.Message` + The message to process commands for. + """ + if message.author.bot: + return + + ctx = await self.get_context(message) + await self.invoke(ctx) + + async def on_message(self, message): + await self.process_commands(message) + + +class Bot(BotBase, discord.Bot): + """Represents a discord bot. + + This class is a subclass of :class:`discord.Bot` and as a result + anything that you can do with a :class:`discord.Bot` you can do with + this bot. + + This class also subclasses :class:`.GroupMixin` to provide the functionality + to manage commands. + + Attributes + ----------- + command_prefix + The command prefix is what the message content must contain initially + to have a command invoked. This prefix could either be a string to + indicate what the prefix should be, or a callable that takes in the bot + as its first parameter and :class:`discord.Message` as its second + parameter and returns the prefix. This is to facilitate "dynamic" + command prefixes. This callable can be either a regular function or + a coroutine. + + An empty string as the prefix always matches, enabling prefix-less + command invocation. While this may be useful in DMs it should be avoided + in servers, as it's likely to cause performance issues and unintended + command invocations. + + The command prefix could also be an iterable of strings indicating that + multiple checks for the prefix should be used and the first one to + match will be the invocation prefix. You can get this prefix via + :attr:`.Context.prefix`. To avoid confusion empty iterables are not + allowed. + + .. note:: + + When passing multiple prefixes be careful to not pass a prefix + that matches a longer prefix occurring later in the sequence. For + example, if the command prefix is ``('!', '!?')`` the ``'!?'`` + prefix will never be matched to any message as the previous one + matches messages starting with ``!?``. This is especially important + when passing an empty string, it should always be last as no prefix + after it will be matched. + case_insensitive: :class:`bool` + Whether the commands should be case insensitive. Defaults to ``False``. This + attribute does not carry over to groups. You must set it to every group if + you require group commands to be case insensitive as well. + help_command: Optional[:class:`.HelpCommand`] + The help command implementation to use. This can be dynamically + set at runtime. To remove the help command pass ``None``. For more + information on implementing a help command, see :ref:`ext_commands_help_command`. + strip_after_prefix: :class:`bool` + Whether to strip whitespace characters after encountering the command + prefix. This allows for ``! hello`` and ``!hello`` to both work if + the ``command_prefix`` is set to ``!``. Defaults to ``False``. + + .. versionadded:: 1.7 + """ + pass + +class AutoShardedBot(BotBase, discord.AutoShardedBot): + """This is similar to :class:`.Bot` except that it is inherited from + :class:`discord.AutoShardedBot` instead. + """ + pass diff --git a/build/lib/discord/ext/commands/cog.py b/build/lib/discord/ext/commands/cog.py new file mode 100644 index 0000000000..2ea7156211 --- /dev/null +++ b/build/lib/discord/ext/commands/cog.py @@ -0,0 +1,69 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import discord +from ...cog import Cog + +from typing import Any, Callable, Generator, TYPE_CHECKING, TypeVar, Type + +from ...commands import ApplicationCommand + +if TYPE_CHECKING: + from .core import Command + +__all__ = ('Cog',) + +CogT = TypeVar('CogT', bound='Cog') +FuncT = TypeVar('FuncT', bound=Callable[..., Any]) + +MISSING: Any = discord.utils.MISSING + +class Cog(Cog): + def __new__(cls: Type[CogT], *args: Any, **kwargs: Any) -> CogT: + # For issue 426, we need to store a copy of the command objects + # since we modify them to inject `self` to them. + # To do this, we need to interfere with the Cog creation process. + self = super().__new__(cls) + + return self + + def walk_commands(self) -> Generator[Command, None, None]: + """An iterator that recursively walks through this cog's commands and subcommands. + + Yields + ------ + Union[:class:`.Command`, :class:`.Group`] + A command or group from the cog. + """ + from .core import GroupMixin + for command in self.__cog_commands__: + if isinstance(command, ApplicationCommand): + yield command + else: + if command.parent is None: + yield command + if isinstance(command, GroupMixin): + yield from command.walk_commands() diff --git a/build/lib/discord/ext/commands/context.py b/build/lib/discord/ext/commands/context.py new file mode 100644 index 0000000000..df42fca37d --- /dev/null +++ b/build/lib/discord/ext/commands/context.py @@ -0,0 +1,401 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import inspect +import re + +from typing import Any, Dict, Generic, List, Optional, TYPE_CHECKING, TypeVar, Union + +import discord.abc +import discord.utils + +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__ = ( + 'Context', +) + +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. + + This class contains a lot of meta data to help you understand more about + the invocation context. This class is not created manually and is instead + passed around to commands as the first parameter. + + This class implements the :class:`~discord.abc.Messageable` ABC. + + Attributes + ----------- + message: :class:`.Message` + The message that triggered the command being executed. + bot: :class:`.Bot` + The bot that contains the command being executed. + args: :class:`list` + The list of transformed arguments that were passed into the command. + If this is accessed during the :func:`.on_command_error` event + then this list could be incomplete. + kwargs: :class:`dict` + A dictionary of transformed arguments that were passed into the command. + Similar to :attr:`args`\, if this is accessed in the + :func:`.on_command_error` event then this dict could be incomplete. + current_parameter: Optional[:class:`inspect.Parameter`] + The parameter that is currently being inspected and converted. + This is only of use for within converters. + + .. versionadded:: 2.0 + prefix: Optional[:class:`str`] + The prefix that was used to invoke the command. + command: Optional[:class:`Command`] + The command that is being invoked currently. + invoked_with: Optional[:class:`str`] + The command name that triggered this invocation. Useful for finding out + which alias called the command. + invoked_parents: List[:class:`str`] + The command names of the parents that triggered this invocation. Useful for + finding out which aliases called the command. + + For example in commands ``?a b c test``, the invoked parents are ``['a', 'b', 'c']``. + + .. versionadded:: 1.7 + + invoked_subcommand: Optional[:class:`Command`] + The subcommand that was invoked. + If no valid subcommand was invoked then this is equal to ``None``. + subcommand_passed: Optional[:class:`str`] + The string that was attempted to call a subcommand. This does not have + to point to a valid registered subcommand and could just point to a + nonsense string. If nothing was passed to attempt a call to a + subcommand then this is set to ``None``. + command_failed: :class:`bool` + A boolean that indicates if the command failed to be parsed, checked, + or invoked. + """ + + def __init__(self, + *, + message: Message, + bot: BotT, + view: StringView, + args: List[Any] = MISSING, + kwargs: Dict[str, Any] = MISSING, + prefix: Optional[str] = None, + command: Optional[Command] = None, + invoked_with: Optional[str] = None, + invoked_parents: List[str] = MISSING, + invoked_subcommand: Optional[Command] = None, + subcommand_passed: Optional[str] = None, + command_failed: bool = False, + 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: Command[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T: + r"""|coro| + + Calls a command with the arguments given. + + This is useful if you want to just call the callback that a + :class:`.Command` holds internally. + + .. note:: + + This does not handle converters, checks, cooldowns, pre-invoke, + or after-invoke hooks in any matter. It calls the internal callback + directly as-if it was a regular function. + + You must take care in passing the proper arguments when + using this function. + + Parameters + ----------- + command: :class:`.Command` + The command that is going to be called. + \*args + The arguments to use. + \*\*kwargs + The keyword arguments to use. + + Raises + ------- + TypeError + The command argument to invoke is missing. + """ + return await command(self, *args, **kwargs) + + async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> None: + """|coro| + + Calls the command again. + + This is similar to :meth:`~.Context.invoke` except that it bypasses + checks, cooldowns, and error handlers. + + .. note:: + + If you want to bypass :exc:`.UserInputError` derived exceptions, + it is recommended to use the regular :meth:`~.Context.invoke` + as it will work more naturally. After all, this will end up + using the old arguments the user has used and will thus just + fail again. + + Parameters + ------------ + call_hooks: :class:`bool` + Whether to call the before and after invoke hooks. + restart: :class:`bool` + Whether to start the call chain from the very beginning + or where we left off (i.e. the command that caused the error). + The default is to start where we left off. + + Raises + ------- + ValueError + The context to reinvoke is not valid. + """ + cmd = self.command + view = self.view + if cmd is None: + raise ValueError('This context is not valid.') + + # some state to revert to when we're done + index, previous = view.index, view.previous + invoked_with = self.invoked_with + invoked_subcommand = self.invoked_subcommand + invoked_parents = self.invoked_parents + subcommand_passed = self.subcommand_passed + + if restart: + to_call = cmd.root_parent or cmd + view.index = len(self.prefix or '') + view.previous = 0 + self.invoked_parents = [] + self.invoked_with = view.get_word() # advance to get the root command + else: + to_call = cmd + + try: + await to_call.reinvoke(self, call_hooks=call_hooks) + finally: + self.command = cmd + view.index = index + view.previous = previous + self.invoked_with = invoked_with + self.invoked_subcommand = invoked_subcommand + self.invoked_parents = invoked_parents + self.subcommand_passed = subcommand_passed + + @property + def valid(self) -> bool: + """: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 + + async def _get_channel(self) -> discord.abc.Messageable: + return self.channel + + @property + def clean_prefix(self) -> str: + """:class:`str`: The cleaned up invoke prefix. i.e. mentions are ``@name`` instead of ``<@id>``. + + .. versionadded:: 2.0 + """ + if self.prefix is None: + return '' + + user = self.me + # 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 + # for this common use case rather than waste performance for the + # odd one. + pattern = re.compile(r"<@!?%s>" % user.id) + return pattern.sub("@%s" % user.display_name.replace('\\', r'\\'), self.prefix) + + @property + def cog(self) -> Optional[Cog]: + """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. None if it does not exist.""" + + if self.command is None: + return None + return self.command.cog + + @discord.utils.cached_property + def guild(self) -> Optional[Guild]: + """Optional[:class:`.Guild`]: Returns the guild associated with this context's command. None if not available.""" + return self.message.guild + + @discord.utils.cached_property + def channel(self) -> MessageableChannel: + """Union[:class:`.abc.Messageable`]: Returns the channel associated with this context's command. + Shorthand for :attr:`.Message.channel`. + """ + return self.message.channel + + @discord.utils.cached_property + def author(self) -> Union[User, Member]: + """Union[:class:`~discord.User`, :class:`.Member`]: + Returns the author associated with this context's command. Shorthand for :attr:`.Message.author` + """ + return self.message.author + + @discord.utils.cached_property + def me(self) -> Union[Member, ClientUser]: + """Union[:class:`.Member`, :class:`.ClientUser`]: + Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message contexts. + """ + # 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 + def voice_client(self) -> Optional[VoiceProtocol]: + r"""Optional[:class:`.VoiceProtocol`]: A shortcut to :attr:`.Guild.voice_client`\, if applicable.""" + g = self.guild + return g.voice_client if g else None + + async def send_help(self, *args: Any) -> Any: + """send_help(entity=) + + |coro| + + Shows the help command for the specified entity if given. + The entity can be a command or a cog. + + If no entity is given, then it'll show help for the + entire bot. + + If the entity is a string, then it looks up whether it's a + :class:`Cog` or a :class:`Command`. + + .. note:: + + Due to the way this function works, instead of returning + something similar to :meth:`~.commands.HelpCommand.command_not_found` + this returns :class:`None` on bad input or no help command. + + Parameters + ------------ + entity: Optional[Union[:class:`Command`, :class:`Cog`, :class:`str`]] + The entity to show help for. + + Returns + -------- + Any + The result of the help command, if any. + """ + from .core import Group, Command, wrap_callback + from .errors import CommandError + + bot = self.bot + cmd = bot.help_command + + if cmd is None: + return None + + cmd = cmd.copy() + cmd.context = self + if len(args) == 0: + await cmd.prepare_help_command(self, None) + mapping = cmd.get_bot_mapping() + injected = wrap_callback(cmd.send_bot_help) + try: + return await injected(mapping) + except CommandError as e: + await cmd.on_help_command_error(self, e) + return None + + entity = args[0] + if isinstance(entity, str): + entity = bot.get_cog(entity) or bot.get_command(entity) + + if entity is None: + return None + + try: + entity.qualified_name + except AttributeError: + # if we're here then it's not a cog, group, or command. + return None + + await cmd.prepare_help_command(self, entity.qualified_name) + + try: + if hasattr(entity, '__cog_commands__'): + injected = wrap_callback(cmd.send_cog_help) + return await injected(entity) + elif isinstance(entity, Group): + injected = wrap_callback(cmd.send_group_help) + return await injected(entity) + elif isinstance(entity, Command): + injected = wrap_callback(cmd.send_command_help) + return await injected(entity) + else: + return None + except CommandError as e: + await cmd.on_help_command_error(self, e) + + @discord.utils.copy_doc(Message.reply) + async def reply(self, content: Optional[str] = None, **kwargs: Any) -> Message: + return await self.message.reply(content, **kwargs) diff --git a/build/lib/discord/ext/commands/converter.py b/build/lib/discord/ext/commands/converter.py new file mode 100644 index 0000000000..2f42392e79 --- /dev/null +++ b/build/lib/discord/ext/commands/converter.py @@ -0,0 +1,1183 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 + +import re +import inspect +from typing import ( + Any, + Dict, + Generic, + Iterable, + Literal, + Optional, + TYPE_CHECKING, + List, + Protocol, + Type, + TypeVar, + Tuple, + Union, + runtime_checkable, +) + +import discord +from .errors import * + +if TYPE_CHECKING: + from .context import Context + from discord.message import PartialMessageableChannel + + +__all__ = ( + 'Converter', + 'ObjectConverter', + 'MemberConverter', + 'UserConverter', + 'MessageConverter', + 'PartialMessageConverter', + 'TextChannelConverter', + 'InviteConverter', + 'GuildConverter', + 'RoleConverter', + 'GameConverter', + 'ColourConverter', + 'ColorConverter', + 'VoiceChannelConverter', + 'StageChannelConverter', + 'EmojiConverter', + 'PartialEmojiConverter', + 'CategoryChannelConverter', + 'IDConverter', + 'StoreChannelConverter', + 'ThreadConverter', + 'GuildChannelConverter', + 'GuildStickerConverter', + 'clean_content', + 'Greedy', + 'run_converters', +) + + +def _get_from_guilds(bot, getter, argument): + result = None + for guild in bot.guilds: + result = getattr(guild, getter)(argument) + if result: + return result + return result + + +_utils_get = discord.utils.get +T = TypeVar('T') +T_co = TypeVar('T_co', covariant=True) +CT = TypeVar('CT', bound=discord.abc.GuildChannel) +TT = TypeVar('TT', bound=discord.Thread) + + +@runtime_checkable +class Converter(Protocol[T_co]): + """The base class of custom converters that require the :class:`.Context` + to be passed to be useful. + + This allows you to implement converters that function similar to the + special cased ``discord`` classes. + + Classes that derive from this should override the :meth:`~.Converter.convert` + method to do its conversion logic. This method must be a :ref:`coroutine `. + """ + + async def convert(self, ctx: Context, argument: str) -> T_co: + """|coro| + + The method to override to do conversion logic. + + If an error is found while converting, it is recommended to + raise a :exc:`.CommandError` derived exception as it will + properly propagate to the error handlers. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context that the argument is being used in. + argument: :class:`str` + The argument that is being converted. + + Raises + ------- + :exc:`.CommandError` + A generic exception occurred when converting the argument. + :exc:`.BadArgument` + The converter failed to convert the argument. + """ + raise NotImplementedError('Derived classes need to implement this.') + + +_ID_REGEX = re.compile(r'([0-9]{15,20})$') + + +class IDConverter(Converter[T_co]): + @staticmethod + def _get_id_match(argument): + return _ID_REGEX.match(argument) + + +class ObjectConverter(IDConverter[discord.Object]): + """Converts to a :class:`~discord.Object`. + + The argument must follow the valid ID or mention formats (e.g. `<@80088516616269824>`). + + .. versionadded:: 2.0 + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by member, role, or channel mention. + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Object: + match = self._get_id_match(argument) or re.match(r'<(?:@(?:!|&)?|#)([0-9]{15,20})>$', argument) + + if match is None: + raise ObjectNotFound(argument) + + result = int(match.group(1)) + + return discord.Object(id=result) + + +class MemberConverter(IDConverter[discord.Member]): + """Converts to a :class:`~discord.Member`. + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name#discrim + 4. Lookup by name + 5. Lookup by nickname + + .. versionchanged:: 1.5 + Raise :exc:`.MemberNotFound` instead of generic :exc:`.BadArgument` + + .. versionchanged:: 1.5.1 + This converter now lazily fetches members from the gateway and HTTP APIs, + optionally caching the result if :attr:`.MemberCacheFlags.joined` is enabled. + """ + + async def query_member_named(self, guild, argument): + cache = guild._state.member_cache_flags.joined + if len(argument) > 5 and argument[-5] == '#': + username, _, discriminator = argument.rpartition('#') + members = await guild.query_members(username, limit=100, cache=cache) + return discord.utils.get(members, name=username, discriminator=discriminator) + else: + members = await guild.query_members(argument, limit=100, cache=cache) + return discord.utils.find(lambda m: m.name == argument or m.nick == argument, members) + + async def query_member_by_id(self, bot, guild, user_id): + ws = bot._get_websocket(shard_id=guild.shard_id) + cache = guild._state.member_cache_flags.joined + if ws.is_ratelimited(): + # If we're being rate limited on the WS, then fall back to using the HTTP API + # So we don't have to wait ~60 seconds for the query to finish + try: + member = await guild.fetch_member(user_id) + except discord.HTTPException: + return None + + if cache: + guild._add_member(member) + return member + + # If we're not being rate limited then we can use the websocket to actually query + members = await guild.query_members(limit=1, user_ids=[user_id], cache=cache) + if not members: + return None + return members[0] + + async def convert(self, ctx: Context, argument: str) -> discord.Member: + bot = ctx.bot + match = self._get_id_match(argument) or re.match(r'<@!?([0-9]{15,20})>$', argument) + guild = ctx.guild + result = None + user_id = None + if match is None: + # not a mention... + if guild: + result = guild.get_member_named(argument) + else: + result = _get_from_guilds(bot, 'get_member_named', argument) + else: + user_id = int(match.group(1)) + if guild: + result = guild.get_member(user_id) or _utils_get(ctx.message.mentions, id=user_id) + else: + result = _get_from_guilds(bot, 'get_member', user_id) + + if result is None: + if guild is None: + raise MemberNotFound(argument) + + if user_id is not None: + result = await self.query_member_by_id(bot, guild, user_id) + else: + result = await self.query_member_named(guild, argument) + + if not result: + raise MemberNotFound(argument) + + return result + + +class UserConverter(IDConverter[discord.User]): + """Converts to a :class:`~discord.User`. + + All lookups are via the global user cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name#discrim + 4. Lookup by name + + .. versionchanged:: 1.5 + Raise :exc:`.UserNotFound` instead of generic :exc:`.BadArgument` + + .. versionchanged:: 1.6 + This converter now lazily fetches users from the HTTP APIs if an ID is passed + and it's not available in cache. + """ + + async def convert(self, ctx: Context, argument: str) -> discord.User: + match = self._get_id_match(argument) or re.match(r'<@!?([0-9]{15,20})>$', argument) + result = None + state = ctx._state + + if match is not None: + user_id = int(match.group(1)) + result = ctx.bot.get_user(user_id) or _utils_get(ctx.message.mentions, id=user_id) + if result is None: + try: + result = await ctx.bot.fetch_user(user_id) + except discord.HTTPException: + raise UserNotFound(argument) from None + + return result + + arg = argument + + # Remove the '@' character if this is the first character from the argument + if arg[0] == '@': + # Remove first character + arg = arg[1:] + + # check for discriminator if it exists, + if len(arg) > 5 and arg[-5] == '#': + discrim = arg[-4:] + name = arg[:-5] + predicate = lambda u: u.name == name and u.discriminator == discrim + result = discord.utils.find(predicate, state._users.values()) + if result is not None: + return result + + predicate = lambda u: u.name == arg + result = discord.utils.find(predicate, state._users.values()) + + if result is None: + raise UserNotFound(argument) + + return result + + +class PartialMessageConverter(Converter[discord.PartialMessage]): + """Converts to a :class:`discord.PartialMessage`. + + .. versionadded:: 1.7 + + The creation strategy is as follows (in order): + + 1. By "{channel ID}-{message ID}" (retrieved by shift-clicking on "Copy ID") + 2. By message ID (The message is assumed to be in the context channel.) + 3. By message URL + """ + + @staticmethod + def _get_id_matches(ctx, argument): + id_regex = re.compile(r'(?:(?P[0-9]{15,20})-)?(?P[0-9]{15,20})$') + link_regex = re.compile( + r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/' + r'(?P[0-9]{15,20}|@me)' + r'/(?P[0-9]{15,20})/(?P[0-9]{15,20})/?$' + ) + match = id_regex.match(argument) or link_regex.match(argument) + if not match: + raise MessageNotFound(argument) + data = match.groupdict() + 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: + guild_id, message_id, channel_id = self._get_id_matches(ctx, argument) + channel = self._resolve_channel(ctx, guild_id, channel_id) + if not channel: + raise ChannelNotFound(channel_id) + return discord.PartialMessage(channel=channel, id=message_id) + + +class MessageConverter(IDConverter[discord.Message]): + """Converts to a :class:`discord.Message`. + + .. versionadded:: 1.1 + + The lookup strategy is as follows (in order): + + 1. Lookup by "{channel ID}-{message ID}" (retrieved by shift-clicking on "Copy ID") + 2. Lookup by message ID (the message **must** be in the context channel) + 3. Lookup by message URL + + .. versionchanged:: 1.5 + Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Message: + guild_id, message_id, channel_id = PartialMessageConverter._get_id_matches(ctx, argument) + message = ctx.bot._connection._get_message(message_id) + if message: + return message + channel = PartialMessageConverter._resolve_channel(ctx, guild_id, channel_id) + if not channel: + raise ChannelNotFound(channel_id) + try: + return await channel.fetch_message(message_id) + except discord.NotFound: + raise MessageNotFound(argument) + except discord.Forbidden: + raise ChannelNotReadable(channel) + + +class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]): + """Converts to a :class:`~discord.abc.GuildChannel`. + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name. + + .. versionadded:: 2.0 + """ + + async def convert(self, ctx: Context, argument: str) -> discord.abc.GuildChannel: + return self._resolve_channel(ctx, argument, 'channels', discord.abc.GuildChannel) + + @staticmethod + def _resolve_channel(ctx: Context, argument: str, attribute: str, type: Type[CT]) -> CT: + 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[CT] = getattr(guild, attribute) + result: Optional[CT] = discord.utils.get(iterable, name=argument) + else: + + def check(c): + return isinstance(c, type) and c.name == argument + + result = discord.utils.find(check, bot.get_all_channels()) + else: + channel_id = int(match.group(1)) + if guild: + result = guild.get_channel(channel_id) + else: + result = _get_from_guilds(bot, 'get_channel', channel_id) + + if not isinstance(result, type): + raise ChannelNotFound(argument) + + 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]): + """Converts to a :class:`~discord.TextChannel`. + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name + + .. versionchanged:: 1.5 + Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.TextChannel: + return GuildChannelConverter._resolve_channel(ctx, argument, 'text_channels', discord.TextChannel) + + +class VoiceChannelConverter(IDConverter[discord.VoiceChannel]): + """Converts to a :class:`~discord.VoiceChannel`. + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name + + .. versionchanged:: 1.5 + Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.VoiceChannel: + return GuildChannelConverter._resolve_channel(ctx, argument, 'voice_channels', discord.VoiceChannel) + + +class StageChannelConverter(IDConverter[discord.StageChannel]): + """Converts to a :class:`~discord.StageChannel`. + + .. versionadded:: 1.7 + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name + """ + + async def convert(self, ctx: Context, argument: str) -> discord.StageChannel: + return GuildChannelConverter._resolve_channel(ctx, argument, 'stage_channels', discord.StageChannel) + + +class CategoryChannelConverter(IDConverter[discord.CategoryChannel]): + """Converts to a :class:`~discord.CategoryChannel`. + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name + + .. versionchanged:: 1.5 + Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.CategoryChannel: + return GuildChannelConverter._resolve_channel(ctx, argument, 'categories', discord.CategoryChannel) + + +class StoreChannelConverter(IDConverter[discord.StoreChannel]): + """Converts to a :class:`~discord.StoreChannel`. + + All lookups are via the local guild. If in a DM context, then the lookup + is done by the global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name. + + .. versionadded:: 1.7 + """ + + async def convert(self, ctx: Context, argument: str) -> 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]): + """Converts to a :class:`~discord.Colour`. + + .. versionchanged:: 1.5 + Add an alias named ColorConverter + + The following formats are accepted: + + - ``0x`` + - ``#`` + - ``0x#`` + - ``rgb(, , )`` + - Any of the ``classmethod`` in :class:`~discord.Colour` + + - The ``_`` in the name can be optionally replaced with spaces. + + Like CSS, ```` can be either 0-255 or 0-100% and ```` can be + either a 6 digit hex number or a 3 digit hex shortcut (e.g. #fff). + + .. versionchanged:: 1.5 + Raise :exc:`.BadColourArgument` instead of generic :exc:`.BadArgument` + + .. versionchanged:: 1.7 + Added support for ``rgb`` function and 3-digit hex shortcuts + """ + + RGB_REGEX = re.compile(r'rgb\s*\((?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*,\s*(?P[0-9]{1,3}%?)\s*\)') + + def parse_hex_number(self, argument): + arg = ''.join(i * 2 for i in argument) if len(argument) == 3 else argument + try: + value = int(arg, base=16) + if not (0 <= value <= 0xFFFFFF): + raise BadColourArgument(argument) + except ValueError: + raise BadColourArgument(argument) + else: + return discord.Color(value=value) + + def parse_rgb_number(self, argument, number): + if number[-1] == '%': + value = int(number[:-1]) + if not (0 <= value <= 100): + raise BadColourArgument(argument) + return round(255 * (value / 100)) + + value = int(number) + if not (0 <= value <= 255): + raise BadColourArgument(argument) + return value + + def parse_rgb(self, argument, *, regex=RGB_REGEX): + match = regex.match(argument) + if match is None: + raise BadColourArgument(argument) + + red = self.parse_rgb_number(argument, match.group('r')) + green = self.parse_rgb_number(argument, match.group('g')) + blue = self.parse_rgb_number(argument, match.group('b')) + return discord.Color.from_rgb(red, green, blue) + + async def convert(self, ctx: Context, argument: str) -> discord.Colour: + if argument[0] == '#': + return self.parse_hex_number(argument[1:]) + + if argument[0:2] == '0x': + rest = argument[2:] + # Legacy backwards compatible syntax + if rest.startswith('#'): + return self.parse_hex_number(rest[1:]) + return self.parse_hex_number(rest) + + arg = argument.lower() + if arg[0:3] == 'rgb': + return self.parse_rgb(arg) + + arg = arg.replace(' ', '_') + method = getattr(discord.Colour, arg, None) + if arg.startswith('from_') or method is None or not inspect.ismethod(method): + raise BadColourArgument(arg) + return method() + + +ColorConverter = ColourConverter + + +class RoleConverter(IDConverter[discord.Role]): + """Converts to a :class:`~discord.Role`. + + All lookups are via the local guild. If in a DM context, the converter raises + :exc:`.NoPrivateMessage` exception. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name + + .. versionchanged:: 1.5 + Raise :exc:`.RoleNotFound` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Role: + guild = ctx.guild + if not guild: + raise NoPrivateMessage() + + match = self._get_id_match(argument) or re.match(r'<@&([0-9]{15,20})>$', argument) + if match: + result = guild.get_role(int(match.group(1))) + else: + result = discord.utils.get(guild._roles.values(), name=argument) + + if result is None: + raise RoleNotFound(argument) + return result + + +class GameConverter(Converter[discord.Game]): + """Converts to :class:`~discord.Game`.""" + + async def convert(self, ctx: Context, argument: str) -> discord.Game: + return discord.Game(name=argument) + + +class InviteConverter(Converter[discord.Invite]): + """Converts to a :class:`~discord.Invite`. + + This is done via an HTTP request using :meth:`.Bot.fetch_invite`. + + .. versionchanged:: 1.5 + Raise :exc:`.BadInviteArgument` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Invite: + try: + invite = await ctx.bot.fetch_invite(argument) + return invite + except Exception as exc: + raise BadInviteArgument(argument) from exc + + +class GuildConverter(IDConverter[discord.Guild]): + """Converts to a :class:`~discord.Guild`. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by name. (There is no disambiguation for Guilds with multiple matching names). + + .. versionadded:: 1.7 + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Guild: + match = self._get_id_match(argument) + result = None + + if match is not None: + guild_id = int(match.group(1)) + result = ctx.bot.get_guild(guild_id) + + if result is None: + result = discord.utils.get(ctx.bot.guilds, name=argument) + + if result is None: + raise GuildNotFound(argument) + return result + + +class EmojiConverter(IDConverter[discord.Emoji]): + """Converts to a :class:`~discord.Emoji`. + + 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. + 2. Lookup by extracting ID from the emoji. + 3. Lookup by name + + .. versionchanged:: 1.5 + Raise :exc:`.EmojiNotFound` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Emoji: + match = self._get_id_match(argument) or re.match(r'$', argument) + result = None + bot = ctx.bot + guild = ctx.guild + + if match is None: + # Try to get the emoji by name. Try local guild first. + if guild: + result = discord.utils.get(guild.emojis, name=argument) + + if result is None: + result = discord.utils.get(bot.emojis, name=argument) + else: + emoji_id = int(match.group(1)) + + # Try to look up emoji by id. + result = bot.get_emoji(emoji_id) + + if result is None: + raise EmojiNotFound(argument) + + return result + + +class PartialEmojiConverter(Converter[discord.PartialEmoji]): + """Converts to a :class:`~discord.PartialEmoji`. + + This is done by extracting the animated flag, name and ID from the emoji. + + .. versionchanged:: 1.5 + Raise :exc:`.PartialEmojiConversionFailure` instead of generic :exc:`.BadArgument` + """ + + async def convert(self, ctx: Context, argument: str) -> discord.PartialEmoji: + match = re.match(r'<(a?):([a-zA-Z0-9\_]{1,32}):([0-9]{15,20})>$', argument) + + if match: + emoji_animated = bool(match.group(1)) + emoji_name = match.group(2) + emoji_id = int(match.group(3)) + + return discord.PartialEmoji.with_state( + ctx.bot._connection, animated=emoji_animated, name=emoji_name, id=emoji_id + ) + + 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]): + """Converts the argument to mention scrubbed version of + said content. + + This behaves similarly to :attr:`~discord.Message.clean_content`. + + Attributes + ------------ + fix_channel_mentions: :class:`bool` + Whether to clean channel mentions. + use_nicknames: :class:`bool` + Whether to use nicknames when transforming mentions. + escape_markdown: :class:`bool` + Whether to also escape special markdown characters. + remove_markdown: :class:`bool` + Whether to also remove special markdown characters. This option is not supported with ``escape_markdown`` + + .. versionadded:: 1.7 + """ + + def __init__( + self, + *, + fix_channel_mentions: bool = False, + use_nicknames: bool = True, + escape_markdown: bool = False, + remove_markdown: bool = False, + ) -> None: + self.fix_channel_mentions = fix_channel_mentions + self.use_nicknames = use_nicknames + self.escape_markdown = escape_markdown + self.remove_markdown = remove_markdown + + async def convert(self, ctx: Context, argument: str) -> str: + msg = ctx.message + + if ctx.guild: + + def resolve_member(id: int) -> str: + m = _utils_get(msg.mentions, id=id) or ctx.guild.get_member(id) + return f'@{m.display_name if self.use_nicknames else m.name}' if m else '@deleted-user' + + def resolve_role(id: int) -> str: + r = _utils_get(msg.role_mentions, id=id) or ctx.guild.get_role(id) + return f'@{r.name}' if r else '@deleted-role' + + else: + + def resolve_member(id: int) -> str: + 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: + result = discord.utils.escape_markdown(result) + elif self.remove_markdown: + result = discord.utils.remove_markdown(result) + + # Completely ensure no mentions escape: + return discord.utils.escape_mentions(result) + + +class Greedy(List[T]): + r"""A special converter that greedily consumes arguments until it can't. + As a consequence of this behaviour, most input errors are silently discarded, + since it is used as an indicator of when to stop parsing. + + When a parser error is met the greedy converter stops converting, undoes the + internal string parsing routine, and continues parsing regularly. + + For example, in the following code: + + .. code-block:: python3 + + @commands.command() + async def test(ctx, numbers: Greedy[int], reason: str): + await ctx.send("numbers: {}, reason: {}".format(numbers, reason)) + + An invocation of ``[p]test 1 2 3 4 5 6 hello`` would pass ``numbers`` with + ``[1, 2, 3, 4, 5, 6]`` and ``reason`` with ``hello``\. + + For more information, check :ref:`ext_commands_special_converters`. + """ + + __slots__ = ('converter',) + + def __init__(self, *, converter: T): + self.converter = converter + + def __repr__(self): + converter = getattr(self.converter, '__name__', repr(self.converter)) + return f'Greedy[{converter}]' + + def __class_getitem__(cls, params: Union[Tuple[T], T]) -> Greedy[T]: + if not isinstance(params, tuple): + params = (params,) + if len(params) != 1: + raise TypeError('Greedy[...] only takes a single argument') + converter = params[0] + + origin = getattr(converter, '__origin__', None) + args = getattr(converter, '__args__', ()) + + if not (callable(converter) or isinstance(converter, Converter) or origin is not None): + raise TypeError('Greedy[...] expects a type or a Converter instance.') + + if converter in (str, type(None)) or origin is Greedy: + raise TypeError(f'Greedy[{converter.__name__}] is invalid.') + + if origin is Union and type(None) in args: + raise TypeError(f'Greedy[{converter!r}] is invalid.') + + return cls(converter=converter) + + +def _convert_to_bool(argument: str) -> bool: + lowered = argument.lower() + if lowered in ('yes', 'y', 'true', 't', '1', 'enable', 'on'): + return True + elif lowered in ('no', 'n', 'false', 'f', '0', 'disable', 'off'): + return False + else: + raise BadBoolArgument(lowered) + + +def get_converter(param: inspect.Parameter) -> Any: + converter = param.annotation + if converter is param.empty: + if param.default is not param.empty: + converter = str if param.default is None else type(param.default) + else: + converter = str + return converter + + +_GenericAlias = type(List[T]) + + +def is_generic_type(tp: Any, *, _GenericAlias: Type = _GenericAlias) -> bool: + return isinstance(tp, type) and issubclass(tp, Generic) or isinstance(tp, _GenericAlias) # type: ignore + + +CONVERTER_MAPPING: Dict[Type[Any], Any] = { + discord.Object: ObjectConverter, + discord.Member: MemberConverter, + discord.User: UserConverter, + discord.Message: MessageConverter, + discord.PartialMessage: PartialMessageConverter, + discord.TextChannel: TextChannelConverter, + discord.Invite: InviteConverter, + discord.Guild: GuildConverter, + discord.Role: RoleConverter, + discord.Game: GameConverter, + discord.Colour: ColourConverter, + discord.VoiceChannel: VoiceChannelConverter, + discord.StageChannel: StageChannelConverter, + discord.Emoji: EmojiConverter, + discord.PartialEmoji: PartialEmojiConverter, + discord.CategoryChannel: CategoryChannelConverter, + discord.StoreChannel: StoreChannelConverter, + discord.Thread: ThreadConverter, + discord.abc.GuildChannel: GuildChannelConverter, + discord.GuildSticker: GuildStickerConverter, +} + + +async def _actual_conversion(ctx: Context, converter, argument: str, param: inspect.Parameter): + if converter is bool: + return _convert_to_bool(argument) + + try: + module = converter.__module__ + except AttributeError: + pass + else: + if module is not None and (module.startswith('discord.') and not module.endswith('converter')): + converter = CONVERTER_MAPPING.get(converter, converter) + + try: + if inspect.isclass(converter) and issubclass(converter, Converter): + if inspect.ismethod(converter.convert): + return await converter.convert(ctx, argument) + else: + return await converter().convert(ctx, argument) + elif isinstance(converter, Converter): + return await converter.convert(ctx, argument) + except CommandError: + raise + except Exception as exc: + raise ConversionError(converter, exc) from exc + + try: + return converter(argument) + except CommandError: + raise + except Exception as exc: + try: + name = converter.__name__ + except AttributeError: + name = converter.__class__.__name__ + + raise BadArgument(f'Converting to "{name}" failed for parameter "{param.name}".') from exc + + +async def run_converters(ctx: Context, converter, argument: str, param: inspect.Parameter): + """|coro| + + Runs converters for a given converter, argument, and parameter. + + This function does the same work that the library does under the hood. + + .. versionadded:: 2.0 + + Parameters + ------------ + ctx: :class:`Context` + The invocation context to run the converters under. + converter: Any + The converter to run, this corresponds to the annotation in the function. + argument: :class:`str` + The argument to convert to. + param: :class:`inspect.Parameter` + The parameter being converted. This is mainly for error reporting. + + Raises + ------- + CommandError + The converter failed to convert. + + Returns + -------- + Any + The resulting conversion. + """ + origin = getattr(converter, '__origin__', None) + + if origin is Union: + errors = [] + _NoneType = type(None) + union_args = converter.__args__ + for conv in union_args: + # if we got to this part in the code, then the previous conversions have failed + # so we should just undo the view, return the default, and allow parsing to continue + # with the other parameters + if conv is _NoneType and param.kind != param.VAR_POSITIONAL: + ctx.view.undo() + return None if param.default is param.empty else param.default + + try: + value = await run_converters(ctx, conv, argument, param) + except CommandError as exc: + errors.append(exc) + else: + return value + + # if we're here, then we failed all the converters + raise BadUnionArgument(param, union_args, errors) + + if origin is Literal: + errors = [] + conversions = {} + literal_args = converter.__args__ + for literal in literal_args: + literal_type = type(literal) + try: + value = conversions[literal_type] + except KeyError: + try: + value = await _actual_conversion(ctx, literal_type, argument, param) + except CommandError as exc: + errors.append(exc) + conversions[literal_type] = object() + continue + else: + conversions[literal_type] = value + + if value == literal: + return value + + # if we're here, then we failed to match all the literals + raise BadLiteralArgument(param, literal_args, errors) + + # This must be the last if-clause in the chain of origin checking + # Nearly every type is a generic type within the typing library + # So care must be taken to make sure a more specialised origin handle + # isn't overwritten by the widest if clause + if origin is not None and is_generic_type(converter): + converter = origin + + return await _actual_conversion(ctx, converter, argument, param) diff --git a/build/lib/discord/ext/commands/cooldowns.py b/build/lib/discord/ext/commands/cooldowns.py new file mode 100644 index 0000000000..c906a85a80 --- /dev/null +++ b/build/lib/discord/ext/commands/cooldowns.py @@ -0,0 +1,391 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 Any, Callable, Deque, Dict, Optional, Type, TypeVar, TYPE_CHECKING +from discord.enums import Enum +import time +import asyncio +from collections import deque + +from ...abc import PrivateChannel +from .errors import MaxConcurrencyReached + +if TYPE_CHECKING: + from ...message import Message + +__all__ = ( + 'BucketType', + 'Cooldown', + 'CooldownMapping', + 'DynamicCooldownMapping', + 'MaxConcurrency', +) + +C = TypeVar('C', bound='CooldownMapping') +MC = TypeVar('MC', bound='MaxConcurrency') + +class BucketType(Enum): + default = 0 + user = 1 + guild = 2 + channel = 3 + member = 4 + category = 5 + role = 6 + + def get_key(self, msg: Message) -> Any: + if self is BucketType.user: + return msg.author.id + elif self is BucketType.guild: + return (msg.guild or msg.author).id + elif self is BucketType.channel: + return msg.channel.id + elif self is BucketType.member: + return ((msg.guild and msg.guild.id), msg.author.id) + elif self is BucketType.category: + return (msg.channel.category or msg.channel).id # type: ignore + elif self is BucketType.role: + # 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 + # NOTE: PrivateChannel doesn't actually have an id attribute but we assume we are + # receiving a DMChannel or GroupChannel which inherit from PrivateChannel and do + return (msg.channel if isinstance(msg.channel, PrivateChannel) else msg.author.top_role).id # type: ignore + + def __call__(self, msg: Message) -> Any: + return self.get_key(msg) + + +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') + + def __init__(self, rate: float, per: float) -> None: + self.rate: int = int(rate) + self.per: float = float(per) + self._window: float = 0.0 + self._tokens: int = self.rate + self._last: float = 0.0 + + 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: + current = time.time() + + tokens = self._tokens + + if current > self._window + self.per: + tokens = self.rate + return tokens + + 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() + tokens = self.get_tokens(current) + + if tokens == 0: + return self.per - (current - self._window) + + return 0.0 + + 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() + self._last = current + + self._tokens = self.get_tokens(current) + + # first token used means that we start a new rate limit window + if self._tokens == self.rate: + self._window = current + + # check if we are rate limited + if self._tokens == 0: + return self.per - (current - self._window) + + # we're not so decrement our tokens + self._tokens -= 1 + + def reset(self) -> None: + """Reset the cooldown to its initial state.""" + self._tokens = self.rate + self._last = 0.0 + + 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) + + def __repr__(self) -> str: + return f'' + +class CooldownMapping: + def __init__( + self, + original: Optional[Cooldown], + type: Callable[[Message], Any], + ) -> None: + if not callable(type): + raise TypeError('Cooldown type must be a BucketType or callable') + + self._cache: Dict[Any, Cooldown] = {} + self._cooldown: Optional[Cooldown] = original + self._type: Callable[[Message], Any] = type + + def copy(self) -> CooldownMapping: + ret = CooldownMapping(self._cooldown, self._type) + ret._cache = self._cache.copy() + return ret + + @property + def valid(self) -> bool: + return self._cooldown is not None + + @property + def type(self) -> Callable[[Message], Any]: + return self._type + + @classmethod + def from_cooldown(cls: Type[C], rate, per, type) -> C: + return cls(Cooldown(rate, per), type) + + def _bucket_key(self, msg: Message) -> Any: + return self._type(msg) + + def _verify_cache_integrity(self, current: Optional[float] = None) -> None: + # 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 + # cooldown of 60s and it has not been used in 60s then that key should be deleted + current = current or time.time() + dead_keys = [k for k, v in self._cache.items() if current > v._last + v.per] + for k in dead_keys: + del self._cache[k] + + def create_bucket(self, message: Message) -> Cooldown: + return self._cooldown.copy() # type: ignore + + def get_bucket(self, message: Message, current: Optional[float] = None) -> Cooldown: + if self._type is BucketType.default: + return self._cooldown # type: ignore + + self._verify_cache_integrity(current) + key = self._bucket_key(message) + if key not in self._cache: + bucket = self.create_bucket(message) + if bucket is not None: + self._cache[key] = bucket + else: + bucket = self._cache[key] + + return bucket + + def update_rate_limit(self, message: Message, current: Optional[float] = None) -> Optional[float]: + bucket = self.get_bucket(message, current) + return bucket.update_rate_limit(current) + +class DynamicCooldownMapping(CooldownMapping): + + def __init__( + self, + factory: Callable[[Message], Cooldown], + type: Callable[[Message], Any] + ) -> None: + super().__init__(None, type) + self._factory: Callable[[Message], Cooldown] = factory + + def copy(self) -> DynamicCooldownMapping: + ret = DynamicCooldownMapping(self._factory, self._type) + ret._cache = self._cache.copy() + return ret + + @property + def valid(self) -> bool: + return True + + def create_bucket(self, message: Message) -> Cooldown: + return self._factory(message) + +class _Semaphore: + """This class is a version of a semaphore. + + If you're wondering why asyncio.Semaphore isn't being used, + it's because it doesn't expose the internal value. This internal + value is necessary because I need to support both `wait=True` and + `wait=False`. + + An asyncio.Queue could have been used to do this as well -- but it is + not as inefficient since internally that uses two queues and is a bit + overkill for what is basically a counter. + """ + + __slots__ = ('value', 'loop', '_waiters') + + def __init__(self, number: int) -> None: + self.value: int = number + self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self._waiters: Deque[asyncio.Future] = deque() + + def __repr__(self) -> str: + return f'<_Semaphore value={self.value} waiters={len(self._waiters)}>' + + def locked(self) -> bool: + return self.value == 0 + + def is_active(self) -> bool: + return len(self._waiters) > 0 + + def wake_up(self) -> None: + while self._waiters: + future = self._waiters.popleft() + if not future.done(): + future.set_result(None) + return + + async def acquire(self, *, wait: bool = False) -> bool: + if not wait and self.value <= 0: + # signal that we're not acquiring + return False + + while self.value <= 0: + future = self.loop.create_future() + self._waiters.append(future) + try: + await future + except: + future.cancel() + if self.value > 0 and not future.cancelled(): + self.wake_up() + raise + + self.value -= 1 + return True + + def release(self) -> None: + self.value += 1 + self.wake_up() + +class MaxConcurrency: + __slots__ = ('number', 'per', 'wait', '_mapping') + + def __init__(self, number: int, *, per: BucketType, wait: bool) -> None: + self._mapping: Dict[Any, _Semaphore] = {} + self.per: BucketType = per + self.number: int = number + self.wait: bool = wait + + if number <= 0: + raise ValueError('max_concurrency \'number\' cannot be less than 1') + + if not isinstance(per, BucketType): + raise TypeError(f'max_concurrency \'per\' must be of type BucketType not {type(per)!r}') + + def copy(self: MC) -> MC: + return self.__class__(self.number, per=self.per, wait=self.wait) + + def __repr__(self) -> str: + return f'' + + def get_key(self, message: Message) -> Any: + return self.per.get_key(message) + + async def acquire(self, message: Message) -> None: + key = self.get_key(message) + + try: + sem = self._mapping[key] + except KeyError: + self._mapping[key] = sem = _Semaphore(self.number) + + acquired = await sem.acquire(wait=self.wait) + if not acquired: + raise MaxConcurrencyReached(self.number, self.per) + + async def release(self, message: Message) -> None: + # Technically there's no reason for this function to be async + # But it might be more useful in the future + key = self.get_key(message) + + try: + sem = self._mapping[key] + except KeyError: + # ...? peculiar + return + else: + sem.release() + + if sem.value >= self.number and not sem.is_active(): + del self._mapping[key] diff --git a/build/lib/discord/ext/commands/core.py b/build/lib/discord/ext/commands/core.py new file mode 100644 index 0000000000..4df5c47e3c --- /dev/null +++ b/build/lib/discord/ext/commands/core.py @@ -0,0 +1,2317 @@ +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +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 ( + Any, + Callable, + Dict, + Generator, + Generic, + Literal, + List, + Optional, + Union, + Set, + Tuple, + TypeVar, + Type, + TYPE_CHECKING, + overload, +) +import asyncio +import functools +import inspect +import datetime +import types + +import discord + +from .errors import * +from ...errors import * +from .cooldowns import Cooldown, BucketType, CooldownMapping, MaxConcurrency, DynamicCooldownMapping +from .converter import run_converters, get_converter, Greedy +from ...commands import _BaseCommand, slash_command, user_command, message_command +from .cog import Cog +from .context import Context + + +if TYPE_CHECKING: + from typing_extensions import Concatenate, ParamSpec, TypeGuard + + from discord.message import Message + + from ._types import ( + Coro, + CoroFunc, + Check, + Hook, + Error, + ) + + +__all__ = ( + 'Command', + 'Group', + 'GroupMixin', + 'command', + 'group', + 'has_role', + 'has_permissions', + 'has_any_role', + 'check', + 'check_any', + 'before_invoke', + 'after_invoke', + 'bot_has_role', + 'bot_has_permissions', + 'bot_has_any_role', + 'cooldown', + 'dynamic_cooldown', + 'max_concurrency', + 'dm_only', + 'guild_only', + 'is_owner', + 'is_nsfw', + 'has_guild_permissions', + 'bot_has_guild_permissions', + 'slash_command', + 'user_command', + 'message_command' +) + +MISSING: Any = discord.utils.MISSING + +T = TypeVar('T') +CogT = TypeVar('CogT', bound='Cog') +CommandT = TypeVar('CommandT', bound='Command') +ContextT = TypeVar('ContextT', bound='Context') +# CHT = TypeVar('CHT', bound='Check') +GroupT = TypeVar('GroupT', bound='Group') +HookT = TypeVar('HookT', bound='Hook') +ErrorT = TypeVar('ErrorT', bound='Error') + +if TYPE_CHECKING: + P = ParamSpec('P') +else: + P = TypeVar('P') + +def unwrap_function(function: Callable[..., Any]) -> Callable[..., Any]: + partial = functools.partial + while True: + if hasattr(function, '__wrapped__'): + function = function.__wrapped__ + elif isinstance(function, partial): + function = function.func + else: + return function + + +def get_signature_parameters(function: Callable[..., Any], globalns: Dict[str, Any]) -> Dict[str, inspect.Parameter]: + signature = inspect.signature(function) + params = {} + cache: Dict[str, Any] = {} + eval_annotation = discord.utils.evaluate_annotation + for name, parameter in signature.parameters.items(): + annotation = parameter.annotation + if annotation is parameter.empty: + params[name] = parameter + continue + if annotation is None: + params[name] = parameter.replace(annotation=type(None)) + continue + + annotation = eval_annotation(annotation, globalns, globalns, cache) + if annotation is Greedy: + raise TypeError('Unparameterized Greedy[...] is disallowed in signature.') + + params[name] = parameter.replace(annotation=annotation) + + return params + + +def wrap_callback(coro): + @functools.wraps(coro) + async def wrapped(*args, **kwargs): + try: + ret = await coro(*args, **kwargs) + except CommandError: + raise + except asyncio.CancelledError: + return + except Exception as exc: + raise CommandInvokeError(exc) from exc + return ret + return wrapped + +def hooked_wrapped_callback(command, ctx, coro): + @functools.wraps(coro) + async def wrapped(*args, **kwargs): + try: + ret = await coro(*args, **kwargs) + except CommandError: + ctx.command_failed = True + raise + except asyncio.CancelledError: + ctx.command_failed = True + return + except Exception as exc: + ctx.command_failed = True + raise CommandInvokeError(exc) from exc + finally: + if command._max_concurrency is not None: + await command._max_concurrency.release(ctx) + + await command.call_after_hooks(ctx) + return ret + return wrapped + + +class _CaseInsensitiveDict(dict): + def __contains__(self, k): + return super().__contains__(k.casefold()) + + def __delitem__(self, k): + return super().__delitem__(k.casefold()) + + def __getitem__(self, k): + return super().__getitem__(k.casefold()) + + def get(self, k, default=None): + return super().get(k.casefold(), default) + + def pop(self, k, default=None): + return super().pop(k.casefold(), default) + + def __setitem__(self, k, v): + super().__setitem__(k.casefold(), v) + +class Command(_BaseCommand, Generic[CogT, P, T]): + r"""A class that implements the protocol for a bot text command. + + These are not created manually, instead they are created via the + decorator or functional interface. + + Attributes + ----------- + name: :class:`str` + The name of the command. + callback: :ref:`coroutine ` + The coroutine that is executed when the command is called. + help: Optional[:class:`str`] + The long help text for the command. + brief: Optional[:class:`str`] + The short help text for the command. + usage: Optional[:class:`str`] + A replacement for arguments in the default help text. + aliases: Union[List[:class:`str`], Tuple[:class:`str`]] + The list of aliases the command can be invoked under. + enabled: :class:`bool` + A boolean that indicates if the command is currently enabled. + If the command is invoked while it is disabled, then + :exc:`.DisabledCommand` is raised to the :func:`.on_command_error` + event. Defaults to ``True``. + parent: Optional[:class:`Group`] + The parent group that this command belongs to. ``None`` if there + isn't one. + cog: Optional[:class:`Cog`] + The cog that this command belongs to. ``None`` if there isn't one. + checks: List[Callable[[:class:`.Context`], :class:`bool`]] + A list of predicates that verifies if the command could be executed + with the given :class:`.Context` as the sole parameter. If an exception + is necessary to be thrown to signal failure, then one inherited from + :exc:`.CommandError` should be used. Note that if the checks fail then + :exc:`.CheckFailure` exception is raised to the :func:`.on_command_error` + event. + description: :class:`str` + The message prefixed into the default help command. + hidden: :class:`bool` + If ``True``\, the default help command does not show this in the + help output. + rest_is_raw: :class:`bool` + If ``False`` and a keyword-only argument is provided then the keyword + only argument is stripped and handled as if it was a regular argument + that handles :exc:`.MissingRequiredArgument` and default values in a + regular matter rather than passing the rest completely raw. If ``True`` + then the keyword-only argument will pass in the rest of the arguments + in a completely raw matter. Defaults to ``False``. + invoked_subcommand: Optional[:class:`Command`] + The subcommand that was invoked, if any. + require_var_positional: :class:`bool` + If ``True`` and a variadic positional argument is specified, requires + the user to specify at least one argument. Defaults to ``False``. + + .. versionadded:: 1.5 + + ignore_extra: :class:`bool` + If ``True``\, ignores extraneous strings passed to a command if all its + requirements are met (e.g. ``?foo a b c`` when only expecting ``a`` + and ``b``). Otherwise :func:`.on_command_error` and local error handlers + are called with :exc:`.TooManyArguments`. Defaults to ``True``. + cooldown_after_parsing: :class:`bool` + If ``True``\, cooldown processing is done after argument parsing, + which calls converters. If ``False`` then cooldown processing is done + first and then the converters are called second. Defaults to ``False``. + extras: :class:`dict` + A dict of user provided extras to attach to the Command. + + .. note:: + This object may be copied by the library. + + + .. versionadded:: 2.0 + + cooldown: Optional[:class:`Cooldown`] + The cooldown applied when the command is invoked. ``None`` if the command + doesn't have a cooldown. + + .. versionadded:: 2.0 + """ + __original_kwargs__: Dict[str, Any] + + def __new__(cls: Type[CommandT], *args: Any, **kwargs: Any) -> CommandT: + # if you're wondering why this is done, it's because we need to ensure + # we have a complete original copy of **kwargs even for classes that + # mess with it by popping before delegating to the subclass __init__. + # In order to do this, we need to control the instance creation and + # inject the original kwargs through __new__ rather than doing it + # inside __init__. + self = super().__new__(cls) + + # we do a shallow copy because it's probably the most common use case. + # this could potentially break if someone modifies a list or something + # while it's in movement, but for now this is the cheapest and + # fastest way to do what we want. + self.__original_kwargs__ = kwargs.copy() + return self + + def __init__(self, func: Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + Callable[Concatenate[ContextT, P], Coro[T]], + ], **kwargs: Any): + if not asyncio.iscoroutinefunction(func): + raise TypeError('Callback must be a coroutine.') + + name = kwargs.get('name') or func.__name__ + if not isinstance(name, str): + raise TypeError('Name of a command must be a string.') + self.name: str = name + + self.callback = func + self.enabled: bool = kwargs.get('enabled', True) + + help_doc = kwargs.get('help') + if help_doc is not None: + help_doc = inspect.cleandoc(help_doc) + else: + help_doc = inspect.getdoc(func) + if isinstance(help_doc, bytes): + help_doc = help_doc.decode('utf-8') + + self.help: Optional[str] = help_doc + + self.brief: Optional[str] = kwargs.get('brief') + self.usage: Optional[str] = kwargs.get('usage') + self.rest_is_raw: bool = kwargs.get('rest_is_raw', False) + self.aliases: Union[List[str], Tuple[str]] = kwargs.get('aliases', []) + self.extras: Dict[str, Any] = kwargs.get('extras', {}) + + if not isinstance(self.aliases, (list, tuple)): + raise TypeError("Aliases of a command must be a list or a tuple of strings.") + + self.description: str = inspect.cleandoc(kwargs.get('description', '')) + self.hidden: bool = kwargs.get('hidden', False) + + try: + checks = func.__commands_checks__ + checks.reverse() + except AttributeError: + checks = kwargs.get('checks', []) + + self.checks: List[Check] = checks + + try: + cooldown = func.__commands_cooldown__ + except AttributeError: + cooldown = kwargs.get('cooldown') + + if cooldown is None: + buckets = CooldownMapping(cooldown, BucketType.default) + elif isinstance(cooldown, CooldownMapping): + buckets = cooldown + else: + raise TypeError("Cooldown must be a an instance of CooldownMapping or None.") + self._buckets: CooldownMapping = buckets + + try: + max_concurrency = func.__commands_max_concurrency__ + except AttributeError: + max_concurrency = kwargs.get('max_concurrency') + + self._max_concurrency: Optional[MaxConcurrency] = max_concurrency + + self.require_var_positional: bool = kwargs.get('require_var_positional', False) + self.ignore_extra: bool = kwargs.get('ignore_extra', True) + self.cooldown_after_parsing: bool = kwargs.get('cooldown_after_parsing', False) + self.cog: Optional[CogT] = None + + # bandaid for the fact that sometimes parent can be the bot instance + parent = kwargs.get('parent') + self.parent: Optional[GroupMixin] = parent if isinstance(parent, _BaseCommand) else None # type: ignore + + self._before_invoke: Optional[Hook] = None + try: + before_invoke = func.__before_invoke__ + except AttributeError: + pass + else: + self.before_invoke(before_invoke) + + self._after_invoke: Optional[Hook] = None + try: + after_invoke = func.__after_invoke__ + except AttributeError: + pass + else: + self.after_invoke(after_invoke) + + @property + def callback(self) -> Union[ + Callable[Concatenate[CogT, Context, P], Coro[T]], + Callable[Concatenate[Context, P], Coro[T]], + ]: + return self._callback + + @callback.setter + def callback(self, function: Union[ + Callable[Concatenate[CogT, Context, P], Coro[T]], + Callable[Concatenate[Context, P], Coro[T]], + ]) -> None: + self._callback = function + unwrap = unwrap_function(function) + self.module = unwrap.__module__ + + try: + globalns = unwrap.__globals__ + except AttributeError: + globalns = {} + + self.params = get_signature_parameters(function, globalns) + + def add_check(self, func: Check) -> None: + """Adds a check to the command. + + This is the non-decorator interface to :func:`.check`. + + .. versionadded:: 1.3 + + Parameters + ----------- + func + The function that will be used as a check. + """ + + self.checks.append(func) + + def remove_check(self, func: Check) -> None: + """Removes a check from the command. + + This function is idempotent and will not raise an exception + if the function is not in the command's checks. + + .. versionadded:: 1.3 + + Parameters + ----------- + func + The function to remove from the checks. + """ + + try: + self.checks.remove(func) + except ValueError: + pass + + def update(self, **kwargs: Any) -> None: + """Updates :class:`Command` instance with updated attribute. + + This works similarly to the :func:`.command` decorator in terms + of parameters in that they are passed to the :class:`Command` or + subclass constructors, sans the name and callback. + """ + self.__init__(self.callback, **dict(self.__original_kwargs__, **kwargs)) + + async def __call__(self, context: Context, *args: P.args, **kwargs: P.kwargs) -> T: + """|coro| + + Calls the internal callback that the command holds. + + .. note:: + + This bypasses all mechanisms -- including checks, converters, + invoke hooks, cooldowns, etc. You must take care to pass + the proper arguments and types to this function. + + .. versionadded:: 1.3 + """ + if self.cog is not None: + return await self.callback(self.cog, context, *args, **kwargs) # type: ignore + else: + return await self.callback(context, *args, **kwargs) # type: ignore + + def _ensure_assignment_on_copy(self, other: CommandT) -> CommandT: + other._before_invoke = self._before_invoke + other._after_invoke = self._after_invoke + if self.checks != other.checks: + other.checks = self.checks.copy() + if self._buckets.valid and not other._buckets.valid: + other._buckets = self._buckets.copy() + if self._max_concurrency != other._max_concurrency: + # _max_concurrency won't be None at this point + other._max_concurrency = self._max_concurrency.copy() # type: ignore + + try: + other.on_error = self.on_error + except AttributeError: + pass + return other + + def copy(self: CommandT) -> CommandT: + """Creates a copy of this command. + + Returns + -------- + :class:`Command` + A new instance of this command. + """ + ret = self.__class__(self.callback, **self.__original_kwargs__) + return self._ensure_assignment_on_copy(ret) + + def _update_copy(self: CommandT, kwargs: Dict[str, Any]) -> CommandT: + if kwargs: + kw = kwargs.copy() + kw.update(self.__original_kwargs__) + copy = self.__class__(self.callback, **kw) + return self._ensure_assignment_on_copy(copy) + else: + return self.copy() + + async def dispatch_error(self, ctx: Context, error: Exception) -> None: + ctx.command_failed = True + cog = self.cog + try: + coro = self.on_error + except AttributeError: + pass + else: + injected = wrap_callback(coro) + if cog is not None: + await injected(cog, ctx, error) + else: + await injected(ctx, error) + + try: + if cog is not None: + local = Cog._get_overridden_method(cog.cog_command_error) + if local is not None: + wrapped = wrap_callback(local) + await wrapped(ctx, error) + finally: + ctx.bot.dispatch('command_error', ctx, error) + + async def transform(self, ctx: Context, param: inspect.Parameter) -> Any: + required = param.default is param.empty + converter = get_converter(param) + consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw + view = ctx.view + view.skip_ws() + + # The greedy converter is simple -- it keeps going until it fails in which case, + # it undos the view ready for the next parameter to use instead + if isinstance(converter, Greedy): + if param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY): + return await self._transform_greedy_pos(ctx, param, required, converter.converter) + elif param.kind == param.VAR_POSITIONAL: + return await self._transform_greedy_var_pos(ctx, param, converter.converter) + else: + # if we're here, then it's a KEYWORD_ONLY param type + # since this is mostly useless, we'll helpfully transform Greedy[X] + # into just X and do the parsing that way. + converter = converter.converter + + if view.eof: + if param.kind == param.VAR_POSITIONAL: + raise RuntimeError() # break the loop + if required: + if self._is_typing_optional(param.annotation): + return None + if hasattr(converter, '__commands_is_flag__') and converter._can_be_constructible(): + return await converter._construct_default(ctx) + raise MissingRequiredArgument(param) + return param.default + + previous = view.index + if consume_rest_is_special: + argument = view.read_rest().strip() + else: + try: + argument = view.get_quoted_word() + except ArgumentParsingError as exc: + if self._is_typing_optional(param.annotation): + view.index = previous + return None + else: + raise exc + view.previous = previous + + # type-checker fails to narrow argument + return await run_converters(ctx, converter, argument, param) # type: ignore + + async def _transform_greedy_pos(self, ctx: Context, param: inspect.Parameter, required: bool, converter: Any) -> Any: + view = ctx.view + result = [] + while not view.eof: + # for use with a manual undo + previous = view.index + + view.skip_ws() + try: + argument = view.get_quoted_word() + value = await run_converters(ctx, converter, argument, param) # type: ignore + except (CommandError, ArgumentParsingError): + view.index = previous + break + else: + result.append(value) + + if not result and not required: + return param.default + return result + + async def _transform_greedy_var_pos(self, ctx: Context, param: inspect.Parameter, converter: Any) -> Any: + view = ctx.view + previous = view.index + try: + argument = view.get_quoted_word() + value = await run_converters(ctx, converter, argument, param) # type: ignore + except (CommandError, ArgumentParsingError): + view.index = previous + raise RuntimeError() from None # break loop + else: + return value + + @property + def clean_params(self) -> Dict[str, inspect.Parameter]: + """Dict[:class:`str`, :class:`inspect.Parameter`]: + Retrieves the parameter dictionary without the context or self parameters. + + Useful for inspecting signature. + """ + result = self.params.copy() + if self.cog is not None: + # first parameter is self + try: + del result[next(iter(result))] + except StopIteration: + raise ValueError("missing 'self' parameter") from None + + try: + # first/second parameter is context + del result[next(iter(result))] + except StopIteration: + raise ValueError("missing 'context' parameter") from None + + return result + + @property + def full_parent_name(self) -> str: + """:class:`str`: Retrieves the fully qualified parent command name. + + This the base command name required to execute it. For example, + in ``?one two three`` the parent name would be ``one two``. + """ + entries = [] + command = self + # command.parent is type-hinted as GroupMixin some attributes are resolved via MRO + while command.parent is not None: # type: ignore + command = command.parent # type: ignore + entries.append(command.name) # type: ignore + + return ' '.join(reversed(entries)) + + @property + def parents(self) -> List[Group]: + """List[:class:`Group`]: Retrieves the parents of this command. + + If the command has no parents then it returns an empty :class:`list`. + + For example in commands ``?a b c test``, the parents are ``[c, b, a]``. + + .. versionadded:: 1.1 + """ + entries = [] + command = self + while command.parent is not None: # type: ignore + command = command.parent # type: ignore + entries.append(command) + + return entries + + @property + def root_parent(self) -> Optional[Group]: + """Optional[:class:`Group`]: Retrieves the root parent of this command. + + If the command has no parents then it returns ``None``. + + For example in commands ``?a b c test``, the root parent is ``a``. + """ + if not self.parent: + return None + return self.parents[-1] + + @property + def qualified_name(self) -> str: + """:class:`str`: Retrieves the fully qualified command name. + + This is the full parent name with the command name as well. + For example, in ``?one two three`` the qualified name would be + ``one two three``. + """ + + parent = self.full_parent_name + if parent: + return parent + ' ' + self.name + else: + return self.name + + def __str__(self) -> str: + return self.qualified_name + + async def _parse_arguments(self, ctx: Context) -> None: + ctx.args = [ctx] if self.cog is None else [self.cog, ctx] + ctx.kwargs = {} + args = ctx.args + kwargs = ctx.kwargs + + view = ctx.view + iterator = iter(self.params.items()) + + if self.cog is not None: + # we have 'self' as the first parameter so just advance + # the iterator and resume parsing + try: + next(iterator) + except StopIteration: + raise discord.ClientException(f'Callback for {self.name} command is missing "self" parameter.') + + # next we have the 'ctx' as the next parameter + try: + next(iterator) + except StopIteration: + raise discord.ClientException(f'Callback for {self.name} command is missing "ctx" parameter.') + + for name, param in iterator: + ctx.current_parameter = param + if param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY): + transformed = await self.transform(ctx, param) + args.append(transformed) + elif param.kind == param.KEYWORD_ONLY: + # kwarg only param denotes "consume rest" semantics + if self.rest_is_raw: + converter = get_converter(param) + argument = view.read_rest() + kwargs[name] = await run_converters(ctx, converter, argument, param) + else: + kwargs[name] = await self.transform(ctx, param) + break + elif param.kind == param.VAR_POSITIONAL: + if view.eof and self.require_var_positional: + raise MissingRequiredArgument(param) + while not view.eof: + try: + transformed = await self.transform(ctx, param) + args.append(transformed) + except RuntimeError: + break + + if not self.ignore_extra and not view.eof: + raise TooManyArguments('Too many arguments passed to ' + self.qualified_name) + + async def call_before_hooks(self, ctx: Context) -> None: + # now that we're done preparing we can call the pre-command hooks + # first, call the command local hook: + cog = self.cog + if self._before_invoke is not None: + # should be cog if @commands.before_invoke is used + instance = getattr(self._before_invoke, '__self__', cog) + # __self__ only exists for methods, not functions + # however, if @command.before_invoke is used, it will be a function + if instance: + await self._before_invoke(instance, ctx) # type: ignore + else: + await self._before_invoke(ctx) # type: ignore + + # call the cog local hook if applicable: + if cog is not None: + hook = Cog._get_overridden_method(cog.cog_before_invoke) + if hook is not None: + await hook(ctx) + + # call the bot global hook if necessary + hook = ctx.bot._before_invoke + if hook is not None: + await hook(ctx) + + async def call_after_hooks(self, ctx: Context) -> None: + cog = self.cog + if self._after_invoke is not None: + instance = getattr(self._after_invoke, '__self__', cog) + if instance: + await self._after_invoke(instance, ctx) # type: ignore + else: + await self._after_invoke(ctx) # type: ignore + + # call the cog local hook if applicable: + if cog is not None: + hook = Cog._get_overridden_method(cog.cog_after_invoke) + if hook is not None: + await hook(ctx) + + hook = ctx.bot._after_invoke + if hook is not None: + await hook(ctx) + + def _prepare_cooldowns(self, ctx: Context) -> None: + if self._buckets.valid: + dt = ctx.message.edited_at or ctx.message.created_at + current = dt.replace(tzinfo=datetime.timezone.utc).timestamp() + bucket = self._buckets.get_bucket(ctx.message, current) + if bucket is not None: + retry_after = bucket.update_rate_limit(current) + if retry_after: + raise CommandOnCooldown(bucket, retry_after, self._buckets.type) # type: ignore + + async def prepare(self, ctx: Context) -> None: + ctx.command = self + + if not await self.can_run(ctx): + raise CheckFailure(f'The check functions for command {self.qualified_name} failed.') + + if self._max_concurrency is not None: + # For this application, context can be duck-typed as a Message + await self._max_concurrency.acquire(ctx) # type: ignore + + try: + if self.cooldown_after_parsing: + await self._parse_arguments(ctx) + self._prepare_cooldowns(ctx) + else: + self._prepare_cooldowns(ctx) + await self._parse_arguments(ctx) + + await self.call_before_hooks(ctx) + except: + if self._max_concurrency is not None: + await self._max_concurrency.release(ctx) # type: ignore + raise + + @property + def cooldown(self) -> Optional[Cooldown]: + return self._buckets._cooldown + + def is_on_cooldown(self, ctx: Context) -> bool: + """Checks whether the command is currently on cooldown. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context to use when checking the commands cooldown status. + + Returns + -------- + :class:`bool` + A boolean indicating if the command is on cooldown. + """ + if not self._buckets.valid: + return False + + bucket = self._buckets.get_bucket(ctx.message) + dt = ctx.message.edited_at or ctx.message.created_at + current = dt.replace(tzinfo=datetime.timezone.utc).timestamp() + return bucket.get_tokens(current) == 0 + + def reset_cooldown(self, ctx: Context) -> None: + """Resets the cooldown on this command. + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context to reset the cooldown under. + """ + if self._buckets.valid: + bucket = self._buckets.get_bucket(ctx.message) + bucket.reset() + + def get_cooldown_retry_after(self, ctx: Context) -> float: + """Retrieves the amount of seconds before this command can be tried again. + + .. versionadded:: 1.4 + + Parameters + ----------- + ctx: :class:`.Context` + The invocation context to retrieve the cooldown from. + + Returns + -------- + :class:`float` + The amount of time left on this command's cooldown in seconds. + If this is ``0.0`` then the command isn't on cooldown. + """ + if self._buckets.valid: + bucket = self._buckets.get_bucket(ctx.message) + dt = ctx.message.edited_at or ctx.message.created_at + current = dt.replace(tzinfo=datetime.timezone.utc).timestamp() + return bucket.get_retry_after(current) + + return 0.0 + + async def invoke(self, ctx: Context) -> None: + await self.prepare(ctx) + + # terminate the invoked_subcommand chain. + # since we're in a regular command (and not a group) then + # the invoked subcommand is None. + ctx.invoked_subcommand = None + ctx.subcommand_passed = None + injected = hooked_wrapped_callback(self, ctx, self.callback) + await injected(*ctx.args, **ctx.kwargs) + + async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None: + ctx.command = self + await self._parse_arguments(ctx) + + if call_hooks: + await self.call_before_hooks(ctx) + + ctx.invoked_subcommand = None + try: + await self.callback(*ctx.args, **ctx.kwargs) # type: ignore + except: + ctx.command_failed = True + raise + finally: + if call_hooks: + await self.call_after_hooks(ctx) + + def error(self, coro: ErrorT) -> ErrorT: + """A decorator that registers a coroutine as a local error handler. + + A local error handler is an :func:`.on_command_error` event limited to + a single command. However, the :func:`.on_command_error` is still + invoked afterwards as the catch-all. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the local error handler. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The error handler must be a coroutine.') + + self.on_error: Error = coro + return coro + + def has_error_handler(self) -> bool: + """:class:`bool`: Checks whether the command has an error handler registered. + + .. versionadded:: 1.7 + """ + return hasattr(self, 'on_error') + + def before_invoke(self, coro: HookT) -> HookT: + """A decorator that registers a coroutine as a pre-invoke hook. + + A pre-invoke hook is called directly before the command is + called. This makes it a useful function to set up database + connections or any type of set up required. + + This pre-invoke hook takes a sole parameter, a :class:`.Context`. + + See :meth:`.Bot.before_invoke` for more info. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the pre-invoke hook. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The pre-invoke hook must be a coroutine.') + + self._before_invoke = coro + return coro + + def after_invoke(self, coro: HookT) -> HookT: + """A decorator that registers a coroutine as a post-invoke hook. + + A post-invoke hook is called directly after the command is + called. This makes it a useful function to clean-up database + connections or any type of clean up required. + + This post-invoke hook takes a sole parameter, a :class:`.Context`. + + See :meth:`.Bot.after_invoke` for more info. + + Parameters + ----------- + coro: :ref:`coroutine ` + The coroutine to register as the post-invoke hook. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine. + """ + if not asyncio.iscoroutinefunction(coro): + raise TypeError('The post-invoke hook must be a coroutine.') + + self._after_invoke = coro + return coro + + @property + def cog_name(self) -> Optional[str]: + """Optional[:class:`str`]: The name of the cog this command belongs to, if any.""" + return type(self.cog).__cog_name__ if self.cog is not None else None + + @property + def short_doc(self) -> str: + """:class:`str`: Gets the "short" documentation of a command. + + By default, this is the :attr:`.brief` attribute. + If that lookup leads to an empty string then the first line of the + :attr:`.help` attribute is used instead. + """ + if self.brief is not None: + return self.brief + if self.help is not None: + return self.help.split('\n', 1)[0] + return '' + + def _is_typing_optional(self, annotation: Union[T, Optional[T]]) -> TypeGuard[Optional[T]]: + return ( + (getattr(annotation, '__origin__', None) is Union + or type(annotation) is getattr(types, "UnionType", Union)) + and type(None) in annotation.__args__ # type: ignore + ) + @property + def signature(self) -> str: + """:class:`str`: Returns a POSIX-like signature useful for help command output.""" + if self.usage is not None: + return self.usage + + params = self.clean_params + if not params: + return '' + + result = [] + for name, param in params.items(): + greedy = isinstance(param.annotation, Greedy) + optional = False # postpone evaluation of if it's an optional argument + + # for typing.Literal[...], typing.Optional[typing.Literal[...]], and Greedy[typing.Literal[...]], the + # parameter signature is a literal list of it's values + annotation = param.annotation.converter if greedy else param.annotation + origin = getattr(annotation, '__origin__', None) + if not greedy and origin is Union: + none_cls = type(None) + union_args = annotation.__args__ + optional = union_args[-1] is none_cls + if len(union_args) == 2 and optional: + annotation = union_args[0] + origin = getattr(annotation, '__origin__', None) + + if origin is Literal: + name = '|'.join(f'"{v}"' if isinstance(v, str) else str(v) for v in annotation.__args__) + if param.default is not param.empty: + # We don't want None or '' to trigger the [name=value] case and instead it should + # do [name] since [name=None] or [name=] are not exactly useful for the user. + should_print = param.default if isinstance(param.default, str) else param.default is not None + if should_print: + result.append(f'[{name}={param.default}]' if not greedy else + f'[{name}={param.default}]...') + continue + else: + result.append(f'[{name}]') + + elif param.kind == param.VAR_POSITIONAL: + if self.require_var_positional: + result.append(f'<{name}...>') + else: + result.append(f'[{name}...]') + elif greedy: + result.append(f'[{name}]...') + elif optional: + result.append(f'[{name}]') + else: + result.append(f'<{name}>') + + return ' '.join(result) + + async def can_run(self, ctx: Context) -> bool: + """|coro| + + Checks if the command can be executed by checking all the predicates + inside the :attr:`~Command.checks` attribute. This also checks whether the + command is disabled. + + .. versionchanged:: 1.3 + Checks whether the command is disabled or not + + Parameters + ----------- + ctx: :class:`.Context` + The ctx of the command currently being invoked. + + Raises + ------- + :class:`CommandError` + Any command error that was raised during a check call will be propagated + by this function. + + Returns + -------- + :class:`bool` + A boolean indicating if the command can be invoked. + """ + + if not self.enabled: + raise DisabledCommand(f'{self.name} command is disabled') + + original = ctx.command + ctx.command = self + + try: + if not await ctx.bot.can_run(ctx): + raise CheckFailure(f'The global check functions for command {self.qualified_name} failed.') + + cog = self.cog + if cog is not None: + local_check = Cog._get_overridden_method(cog.cog_check) + if local_check is not None: + ret = await discord.utils.maybe_coroutine(local_check, ctx) + if not ret: + return False + + predicates = self.checks + if not predicates: + # since we have no checks, then we just return True. + return True + + return await discord.utils.async_all(predicate(ctx) for predicate in predicates) # type: ignore + finally: + ctx.command = original + + def _set_cog(self, cog): + self.cog = cog + +class GroupMixin(Generic[CogT]): + """A mixin that implements common functionality for classes that behave + similar to :class:`.Group` and are allowed to register commands. + + Attributes + ----------- + all_commands: :class:`dict` + A mapping of command name to :class:`.Command` + objects. + case_insensitive: :class:`bool` + Whether the commands should be case insensitive. Defaults to ``False``. + """ + def __init__(self, *args: Any, **kwargs: Any) -> None: + case_insensitive = kwargs.get('case_insensitive', False) + self.all_commands: Dict[str, Command[CogT, Any, Any]] = _CaseInsensitiveDict() if case_insensitive else {} + self.case_insensitive: bool = case_insensitive + super().__init__(*args, **kwargs) + + @property + def commands(self) -> Set[Command[CogT, Any, Any]]: + """Set[:class:`.Command`]: A unique set of commands without aliases that are registered.""" + return set(self.all_commands.values()) + + def recursively_remove_all_commands(self) -> None: + for command in self.all_commands.copy().values(): + if isinstance(command, GroupMixin): + command.recursively_remove_all_commands() + self.remove_command(command.name) + + def add_command(self, command: Command[CogT, Any, Any]) -> None: + """Adds a :class:`.Command` into the internal list of commands. + + This is usually not called, instead the :meth:`~.GroupMixin.command` or + :meth:`~.GroupMixin.group` shortcut decorators are used instead. + + .. versionchanged:: 1.4 + Raise :exc:`.CommandRegistrationError` instead of generic :exc:`.ClientException` + + Parameters + ----------- + command: :class:`Command` + The command to add. + + Raises + ------- + :exc:`.CommandRegistrationError` + If the command or its alias is already registered by different command. + TypeError + If the command passed is not a subclass of :class:`.Command`. + """ + + if not isinstance(command, Command): + raise TypeError('The command passed must be a subclass of Command') + + if isinstance(self, Command): + command.parent = self + + if command.name in self.all_commands: + raise CommandRegistrationError(command.name) + + self.all_commands[command.name] = command + for alias in command.aliases: + if alias in self.all_commands: + self.remove_command(command.name) + raise CommandRegistrationError(alias, alias_conflict=True) + self.all_commands[alias] = command + + def remove_command(self, name: str) -> Optional[Command[CogT, Any, Any]]: + """Remove a :class:`.Command` from the internal list + of commands. + + This could also be used as a way to remove aliases. + + Parameters + ----------- + name: :class:`str` + The name of the command to remove. + + Returns + -------- + Optional[:class:`.Command`] + The command that was removed. If the name is not valid then + ``None`` is returned instead. + """ + command = self.all_commands.pop(name, None) + + # does not exist + if command is None: + return None + + if name in command.aliases: + # we're removing an alias so we don't want to remove the rest + return command + + # we're not removing the alias so let's delete the rest of them. + for alias in command.aliases: + cmd = self.all_commands.pop(alias, None) + # in the case of a CommandRegistrationError, an alias might conflict + # with an already existing command. If this is the case, we want to + # make sure the pre-existing command is not removed. + if cmd is not None and cmd != command: + self.all_commands[alias] = cmd + return command + + def walk_commands(self) -> Generator[Command[CogT, Any, Any], None, None]: + """An iterator that recursively walks through all commands and subcommands. + + .. versionchanged:: 1.4 + Duplicates due to aliases are no longer returned + + Yields + ------ + Union[:class:`.Command`, :class:`.Group`] + A command or group from the internal list of commands. + """ + for command in self.commands: + yield command + if isinstance(command, GroupMixin): + yield from command.walk_commands() + + def get_command(self, name: str) -> Optional[Command[CogT, Any, Any]]: + """Get a :class:`.Command` from the internal list + of commands. + + This could also be used as a way to get aliases. + + The name could be fully qualified (e.g. ``'foo bar'``) will get + the subcommand ``bar`` of the group command ``foo``. If a + subcommand is not found then ``None`` is returned just as usual. + + Parameters + ----------- + name: :class:`str` + The name of the command to get. + + Returns + -------- + Optional[:class:`Command`] + The command that was requested. If not found, returns ``None``. + """ + + # fast path, no space in name. + if ' ' not in name: + return self.all_commands.get(name) + + names = name.split() + if not names: + return None + obj = self.all_commands.get(names[0]) + if not isinstance(obj, GroupMixin): + return obj + + for name in names[1:]: + try: + obj = obj.all_commands[name] # type: ignore + except (AttributeError, KeyError): + return None + + return obj + + @overload + def command( + self, + name: str = ..., + cls: Type[Command[CogT, P, T]] = ..., + *args: Any, + **kwargs: Any, + ) -> Callable[ + [ + Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + Callable[Concatenate[ContextT, P], Coro[T]], + ] + ], Command[CogT, P, T]]: + ... + + @overload + def command( + self, + name: str = ..., + cls: Type[CommandT] = ..., + *args: Any, + **kwargs: Any, + ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], CommandT]: + ... + + def command( + self, + name: str = MISSING, + cls: Type[CommandT] = MISSING, + *args: Any, + **kwargs: Any, + ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], CommandT]: + """A shortcut decorator that invokes :func:`.command` and adds it to + the internal command list via :meth:`~.GroupMixin.add_command`. + + Returns + -------- + Callable[..., :class:`Command`] + A decorator that converts the provided method into a Command, adds it to the bot, then returns it. + """ + def decorator(func: Callable[Concatenate[ContextT, P], Coro[Any]]) -> CommandT: + kwargs.setdefault('parent', self) + result = command(name=name, cls=cls, *args, **kwargs)(func) + self.add_command(result) + return result + + return decorator + + @overload + def group( + self, + name: str = ..., + cls: Type[Group[CogT, P, T]] = ..., + *args: Any, + **kwargs: Any, + ) -> Callable[[ + Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + Callable[Concatenate[ContextT, P], Coro[T]] + ] + ], Group[CogT, P, T]]: + ... + + @overload + def group( + self, + name: str = ..., + cls: Type[GroupT] = ..., + *args: Any, + **kwargs: Any, + ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], GroupT]: + ... + + def group( + self, + name: str = MISSING, + cls: Type[GroupT] = MISSING, + *args: Any, + **kwargs: Any, + ) -> Callable[[Callable[Concatenate[ContextT, P], Coro[Any]]], GroupT]: + """A shortcut decorator that invokes :func:`.group` and adds it to + the internal command list via :meth:`~.GroupMixin.add_command`. + + Returns + -------- + Callable[..., :class:`Group`] + A decorator that converts the provided method into a Group, adds it to the bot, then returns it. + """ + def decorator(func: Callable[Concatenate[ContextT, P], Coro[Any]]) -> GroupT: + kwargs.setdefault('parent', self) + result = group(name=name, cls=cls, *args, **kwargs)(func) + self.add_command(result) + return result + + return decorator + +class Group(GroupMixin[CogT], Command[CogT, P, T]): + """A class that implements a grouping protocol for commands to be + executed as subcommands. + + This class is a subclass of :class:`.Command` and thus all options + valid in :class:`.Command` are valid in here as well. + + Attributes + ----------- + invoke_without_command: :class:`bool` + Indicates if the group callback should begin parsing and + invocation only if no subcommand was found. Useful for + making it an error handling function to tell the user that + no subcommand was found or to have different functionality + in case no subcommand was found. If this is ``False``, then + the group callback will always be invoked first. This means + that the checks and the parsing dictated by its parameters + will be executed. Defaults to ``False``. + case_insensitive: :class:`bool` + Indicates if the group's commands should be case insensitive. + Defaults to ``False``. + """ + def __init__(self, *args: Any, **attrs: Any) -> None: + self.invoke_without_command: bool = attrs.pop('invoke_without_command', False) + super().__init__(*args, **attrs) + + def copy(self: GroupT) -> GroupT: + """Creates a copy of this :class:`Group`. + + Returns + -------- + :class:`Group` + A new instance of this group. + """ + ret = super().copy() + for cmd in self.commands: + ret.add_command(cmd.copy()) + return ret # type: ignore + + async def invoke(self, ctx: Context) -> None: + ctx.invoked_subcommand = None + ctx.subcommand_passed = None + early_invoke = not self.invoke_without_command + if early_invoke: + await self.prepare(ctx) + + view = ctx.view + previous = view.index + view.skip_ws() + trigger = view.get_word() + + if trigger: + ctx.subcommand_passed = trigger + ctx.invoked_subcommand = self.all_commands.get(trigger, None) + + if early_invoke: + injected = hooked_wrapped_callback(self, ctx, self.callback) + await injected(*ctx.args, **ctx.kwargs) + + ctx.invoked_parents.append(ctx.invoked_with) # type: ignore + + if trigger and ctx.invoked_subcommand: + ctx.invoked_with = trigger + await ctx.invoked_subcommand.invoke(ctx) + elif not early_invoke: + # undo the trigger parsing + view.index = previous + view.previous = previous + await super().invoke(ctx) + + async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None: + ctx.invoked_subcommand = None + early_invoke = not self.invoke_without_command + if early_invoke: + ctx.command = self + await self._parse_arguments(ctx) + + if call_hooks: + await self.call_before_hooks(ctx) + + view = ctx.view + previous = view.index + view.skip_ws() + trigger = view.get_word() + + if trigger: + ctx.subcommand_passed = trigger + ctx.invoked_subcommand = self.all_commands.get(trigger, None) + + if early_invoke: + try: + await self.callback(*ctx.args, **ctx.kwargs) # type: ignore + except: + ctx.command_failed = True + raise + finally: + if call_hooks: + await self.call_after_hooks(ctx) + + ctx.invoked_parents.append(ctx.invoked_with) # type: ignore + + if trigger and ctx.invoked_subcommand: + ctx.invoked_with = trigger + await ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks) + elif not early_invoke: + # undo the trigger parsing + view.index = previous + view.previous = previous + await super().reinvoke(ctx, call_hooks=call_hooks) + +# Decorators + +@overload +def command( + name: str = ..., + cls: Type[Command[CogT, P, T]] = ..., + **attrs: Any, +) -> Callable[ + [ + Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + Callable[Concatenate[ContextT, P], Coro[T]], + ] + ] +, Command[CogT, P, T]]: + ... + +@overload +def command( + name: str = ..., + cls: Type[CommandT] = ..., + **attrs: Any, +) -> Callable[ + [ + Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[Any]], + Callable[Concatenate[ContextT, P], Coro[Any]], + ] + ] +, CommandT]: + ... + +def command( + name: str = MISSING, + cls: Type[CommandT] = MISSING, + **attrs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + ] + ] +, Union[Command[CogT, P, T], CommandT]]: + """A decorator that transforms a function into a :class:`.Command` + or if called with :func:`.group`, :class:`.Group`. + + By default the ``help`` attribute is received automatically from the + docstring of the function and is cleaned up with the use of + ``inspect.cleandoc``. If the docstring is ``bytes``, then it is decoded + into :class:`str` using utf-8 encoding. + + All checks added using the :func:`.check` & co. decorators are added into + the function. There is no way to supply your own checks through this + decorator. + + Parameters + ----------- + name: :class:`str` + The name to create the command with. By default this uses the + function name unchanged. + cls + The class to construct with. By default this is :class:`.Command`. + You usually do not change this. + attrs + Keyword arguments to pass into the construction of the class denoted + by ``cls``. + + Raises + ------- + TypeError + If the function is not a coroutine or is already a command. + """ + if cls is MISSING: + cls = Command # type: ignore + + def decorator(func: Union[ + Callable[Concatenate[ContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ContextT, P], Coro[Any]], + ]) -> CommandT: + if isinstance(func, Command): + raise TypeError('Callback is already a command.') + return cls(func, name=name, **attrs) + + return decorator + +@overload +def group( + name: str = ..., + cls: Type[Group[CogT, P, T]] = ..., + **attrs: Any, +) -> Callable[ + [ + Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + Callable[Concatenate[ContextT, P], Coro[T]], + ] + ] +, Group[CogT, P, T]]: + ... + +@overload +def group( + name: str = ..., + cls: Type[GroupT] = ..., + **attrs: Any, +) -> Callable[ + [ + Union[ + Callable[Concatenate[CogT, ContextT, P], Coro[Any]], + Callable[Concatenate[ContextT, P], Coro[Any]], + ] + ] +, GroupT]: + ... + +def group( + name: str = MISSING, + cls: Type[GroupT] = MISSING, + **attrs: Any, +) -> Callable[ + [ + Union[ + Callable[Concatenate[ContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ContextT, P], Coro[T]], + ] + ] +, Union[Group[CogT, P, T], GroupT]]: + """A decorator that transforms a function into a :class:`.Group`. + + This is similar to the :func:`.command` decorator but the ``cls`` + parameter is set to :class:`Group` by default. + + .. versionchanged:: 1.1 + The ``cls`` parameter can now be passed. + """ + if cls is MISSING: + cls = Group # type: ignore + return command(name=name, cls=cls, **attrs) # type: ignore + +def check(predicate: Check) -> Callable[[T], T]: + r"""A decorator that adds a check to the :class:`.Command` or its + subclasses. These checks could be accessed via :attr:`.Command.checks`. + + These checks should be predicates that take in a single parameter taking + a :class:`.Context`. If the check returns a ``False``\-like value then + during invocation a :exc:`.CheckFailure` exception is raised and sent to + the :func:`.on_command_error` event. + + If an exception should be thrown in the predicate then it should be a + subclass of :exc:`.CommandError`. Any exception not subclassed from it + will be propagated while those subclassed will be sent to + :func:`.on_command_error`. + + A special attribute named ``predicate`` is bound to the value + returned by this decorator to retrieve the predicate passed to the + decorator. This allows the following introspection and chaining to be done: + + .. code-block:: python3 + + def owner_or_permissions(**perms): + original = commands.has_permissions(**perms).predicate + async def extended_check(ctx): + if ctx.guild is None: + return False + return ctx.guild.owner_id == ctx.author.id or await original(ctx) + return commands.check(extended_check) + + .. note:: + + The function returned by ``predicate`` is **always** a coroutine, + even if the original function was not a coroutine. + + .. versionchanged:: 1.3 + The ``predicate`` attribute was added. + + Examples + --------- + + Creating a basic check to see if the command invoker is you. + + .. code-block:: python3 + + def check_if_it_is_me(ctx): + return ctx.message.author.id == 85309593344815104 + + @bot.command() + @commands.check(check_if_it_is_me) + async def only_for_me(ctx): + await ctx.send('I know you!') + + Transforming common checks into its own decorator: + + .. code-block:: python3 + + def is_me(): + def predicate(ctx): + return ctx.message.author.id == 85309593344815104 + return commands.check(predicate) + + @bot.command() + @is_me() + async def only_me(ctx): + await ctx.send('Only you!') + + Parameters + ----------- + predicate: Callable[[:class:`Context`], :class:`bool`] + The predicate to check if the command should be invoked. + """ + + def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + if isinstance(func, _BaseCommand): + func.checks.append(predicate) + else: + if not hasattr(func, '__commands_checks__'): + func.__commands_checks__ = [] + + func.__commands_checks__.append(predicate) + + return func + + if inspect.iscoroutinefunction(predicate): + decorator.predicate = predicate + else: + @functools.wraps(predicate) + async def wrapper(ctx): + return predicate(ctx) # type: ignore + decorator.predicate = wrapper + + return decorator # type: ignore + +def check_any(*checks: Check) -> Callable[[T], T]: + r"""A :func:`check` that is added that checks if any of the checks passed + will pass, i.e. using logical OR. + + If all checks fail then :exc:`.CheckAnyFailure` is raised to signal the failure. + It inherits from :exc:`.CheckFailure`. + + .. note:: + + The ``predicate`` attribute for this function **is** a coroutine. + + .. versionadded:: 1.3 + + Parameters + ------------ + \*checks: Callable[[:class:`Context`], :class:`bool`] + An argument list of checks that have been decorated with + the :func:`check` decorator. + + Raises + ------- + TypeError + A check passed has not been decorated with the :func:`check` + decorator. + + Examples + --------- + + Creating a basic check to see if it's the bot owner or + the server owner: + + .. code-block:: python3 + + def is_guild_owner(): + def predicate(ctx): + return ctx.guild is not None and ctx.guild.owner_id == ctx.author.id + return commands.check(predicate) + + @bot.command() + @commands.check_any(commands.is_owner(), is_guild_owner()) + async def only_for_owners(ctx): + await ctx.send('Hello mister owner!') + """ + + unwrapped = [] + for wrapped in checks: + try: + pred = wrapped.predicate + except AttributeError: + raise TypeError(f'{wrapped!r} must be wrapped by commands.check decorator') from None + else: + unwrapped.append(pred) + + async def predicate(ctx: Context) -> bool: + errors = [] + for func in unwrapped: + try: + value = await func(ctx) + except CheckFailure as e: + errors.append(e) + else: + if value: + return True + # if we're here, all checks failed + raise CheckAnyFailure(unwrapped, errors) + + return check(predicate) + +def has_role(item: Union[int, str]) -> Callable[[T], T]: + """A :func:`.check` that is added that checks if the member invoking the + command has the role specified via the name or ID specified. + + If a string is specified, you must give the exact name of the role, including + caps and spelling. + + If an integer is specified, you must give the exact snowflake ID of the role. + + If the message is invoked in a private message context then the check will + return ``False``. + + This check raises one of two special exceptions, :exc:`.MissingRole` if the user + is missing a role, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1 + + Raise :exc:`.MissingRole` or :exc:`.NoPrivateMessage` + instead of generic :exc:`.CheckFailure` + + Parameters + ----------- + item: Union[:class:`int`, :class:`str`] + The name or ID of the role to check. + """ + + def predicate(ctx: Context) -> bool: + if ctx.guild is None: + raise NoPrivateMessage() + + # ctx.guild is None doesn't narrow ctx.author to Member + if isinstance(item, int): + role = discord.utils.get(ctx.author.roles, id=item) # type: ignore + else: + role = discord.utils.get(ctx.author.roles, name=item) # type: ignore + if role is None: + raise MissingRole(item) + return True + + return check(predicate) + +def has_any_role(*items: Union[int, str]) -> Callable[[T], T]: + r"""A :func:`.check` that is added that checks if the member invoking the + command has **any** of the roles specified. This means that if they have + one out of the three roles specified, then this check will return `True`. + + Similar to :func:`.has_role`\, the names or IDs passed in must be exact. + + This check raises one of two special exceptions, :exc:`.MissingAnyRole` if the user + is missing all roles, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1 + + Raise :exc:`.MissingAnyRole` or :exc:`.NoPrivateMessage` + instead of generic :exc:`.CheckFailure` + + Parameters + ----------- + items: List[Union[:class:`str`, :class:`int`]] + An argument list of names or IDs to check that the member has roles wise. + + Example + -------- + + .. code-block:: python3 + + @bot.command() + @commands.has_any_role('Library Devs', 'Moderators', 492212595072434186) + async def cool(ctx): + await ctx.send('You are cool indeed') + """ + def predicate(ctx): + if ctx.guild is None: + raise NoPrivateMessage() + + # ctx.guild is None doesn't narrow ctx.author to Member + getter = functools.partial(discord.utils.get, ctx.author.roles) # type: ignore + if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items): + return True + raise MissingAnyRole(list(items)) + + return check(predicate) + +def bot_has_role(item: int) -> Callable[[T], T]: + """Similar to :func:`.has_role` except checks if the bot itself has the + role. + + This check raises one of two special exceptions, :exc:`.BotMissingRole` if the bot + is missing the role, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1 + + Raise :exc:`.BotMissingRole` or :exc:`.NoPrivateMessage` + instead of generic :exc:`.CheckFailure` + """ + + def predicate(ctx): + if ctx.guild is None: + raise NoPrivateMessage() + + me = ctx.me + if isinstance(item, int): + role = discord.utils.get(me.roles, id=item) + else: + role = discord.utils.get(me.roles, name=item) + if role is None: + raise BotMissingRole(item) + return True + return check(predicate) + +def bot_has_any_role(*items: int) -> Callable[[T], T]: + """Similar to :func:`.has_any_role` except checks if the bot itself has + any of the roles listed. + + This check raises one of two special exceptions, :exc:`.BotMissingAnyRole` if the bot + is missing all roles, or :exc:`.NoPrivateMessage` if it is used in a private message. + Both inherit from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1 + + Raise :exc:`.BotMissingAnyRole` or :exc:`.NoPrivateMessage` + instead of generic checkfailure + """ + def predicate(ctx): + if ctx.guild is None: + raise NoPrivateMessage() + + me = ctx.me + getter = functools.partial(discord.utils.get, me.roles) + if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items): + return True + raise BotMissingAnyRole(list(items)) + return check(predicate) + +def has_permissions(**perms: bool) -> Callable[[T], T]: + """A :func:`.check` that is added that checks if the member has all of + the permissions necessary. + + Note that this check operates on the current channel permissions, not the + guild wide permissions. + + The permissions passed in must be exactly like the properties shown under + :class:`.discord.Permissions`. + + This check raises a special exception, :exc:`.MissingPermissions` + that is inherited from :exc:`.CheckFailure`. + + Parameters + ------------ + perms + An argument list of permissions to check for. + + Example + --------- + + .. code-block:: python3 + + @bot.command() + @commands.has_permissions(manage_messages=True) + async def test(ctx): + await ctx.send('You can manage messages.') + + """ + + invalid = set(perms) - set(discord.Permissions.VALID_FLAGS) + if invalid: + raise TypeError(f"Invalid permission(s): {', '.join(invalid)}") + + def predicate(ctx: Context) -> bool: + ch = ctx.channel + permissions = ch.permissions_for(ctx.author) # type: ignore + + missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + + if not missing: + return True + + raise MissingPermissions(missing) + + return check(predicate) + +def bot_has_permissions(**perms: bool) -> Callable[[T], T]: + """Similar to :func:`.has_permissions` except checks if the bot itself has + the permissions listed. + + This check raises a special exception, :exc:`.BotMissingPermissions` + that is inherited from :exc:`.CheckFailure`. + """ + + invalid = set(perms) - set(discord.Permissions.VALID_FLAGS) + if invalid: + raise TypeError(f"Invalid permission(s): {', '.join(invalid)}") + + def predicate(ctx: Context) -> bool: + guild = ctx.guild + me = guild.me if guild is not None else ctx.bot.user + permissions = ctx.channel.permissions_for(me) # type: ignore + + missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + + if not missing: + return True + + raise BotMissingPermissions(missing) + + return check(predicate) + +def has_guild_permissions(**perms: bool) -> Callable[[T], T]: + """Similar to :func:`.has_permissions`, but operates on guild wide + permissions instead of the current channel permissions. + + If this check is called in a DM context, it will raise an + exception, :exc:`.NoPrivateMessage`. + + .. versionadded:: 1.3 + """ + + invalid = set(perms) - set(discord.Permissions.VALID_FLAGS) + if invalid: + raise TypeError(f"Invalid permission(s): {', '.join(invalid)}") + + def predicate(ctx: Context) -> bool: + if not ctx.guild: + raise NoPrivateMessage + + permissions = ctx.author.guild_permissions # type: ignore + missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + + if not missing: + return True + + raise MissingPermissions(missing) + + return check(predicate) + +def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]: + """Similar to :func:`.has_guild_permissions`, but checks the bot + members guild permissions. + + .. versionadded:: 1.3 + """ + + invalid = set(perms) - set(discord.Permissions.VALID_FLAGS) + if invalid: + raise TypeError(f"Invalid permission(s): {', '.join(invalid)}") + + def predicate(ctx: Context) -> bool: + if not ctx.guild: + raise NoPrivateMessage + + permissions = ctx.me.guild_permissions # type: ignore + missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value] + + if not missing: + return True + + raise BotMissingPermissions(missing) + + return check(predicate) + +def dm_only() -> Callable[[T], T]: + """A :func:`.check` that indicates this command must only be used in a + DM context. Only private messages are allowed when + using the command. + + This check raises a special exception, :exc:`.PrivateMessageOnly` + that is inherited from :exc:`.CheckFailure`. + + .. versionadded:: 1.1 + """ + + def predicate(ctx: Context) -> bool: + if ctx.guild is not None: + raise PrivateMessageOnly() + return True + + return check(predicate) + +def guild_only() -> Callable[[T], T]: + """A :func:`.check` that indicates this command must only be used in a + guild context only. Basically, no private messages are allowed when + using the command. + + This check raises a special exception, :exc:`.NoPrivateMessage` + that is inherited from :exc:`.CheckFailure`. + """ + + def predicate(ctx: Context) -> bool: + if ctx.guild is None: + raise NoPrivateMessage() + return True + + return check(predicate) + +def is_owner() -> Callable[[T], T]: + """A :func:`.check` that checks if the person invoking this command is the + owner of the bot. + + This is powered by :meth:`.Bot.is_owner`. + + This check raises a special exception, :exc:`.NotOwner` that is derived + from :exc:`.CheckFailure`. + """ + + async def predicate(ctx: Context) -> bool: + if not await ctx.bot.is_owner(ctx.author): + raise NotOwner('You do not own this bot.') + return True + + return check(predicate) + +def is_nsfw() -> Callable[[T], T]: + """A :func:`.check` that checks if the channel is a NSFW channel. + + This check raises a special exception, :exc:`.NSFWChannelRequired` + that is derived from :exc:`.CheckFailure`. + + .. versionchanged:: 1.1 + + Raise :exc:`.NSFWChannelRequired` instead of generic :exc:`.CheckFailure`. + DM channels will also now pass this check. + """ + def pred(ctx: Context) -> bool: + ch = ctx.channel + if ctx.guild is None or (isinstance(ch, (discord.TextChannel, discord.Thread)) and ch.is_nsfw()): + return True + raise NSFWChannelRequired(ch) # type: ignore + return check(pred) + +def cooldown(rate: int, per: float, type: Union[BucketType, Callable[[Message], Any]] = BucketType.default) -> Callable[[T], T]: + """A decorator that adds a cooldown to a :class:`.Command` + + A cooldown allows a command to only be used a specific amount + of times in a specific time frame. These cooldowns can be based + either on a per-guild, per-channel, per-user, per-role or global basis. + Denoted by the third argument of ``type`` which must be of enum + type :class:`.BucketType`. + + If a cooldown is triggered, then :exc:`.CommandOnCooldown` is triggered in + :func:`.on_command_error` and the local error handler. + + A command can only have a single cooldown. + + Parameters + ------------ + rate: :class:`int` + The number of times a command can be used before triggering a cooldown. + per: :class:`float` + The amount of seconds to wait for a cooldown when it's been triggered. + type: Union[:class:`.BucketType`, Callable[[:class:`.Message`], Any]] + The type of cooldown to have. If callable, should return a key for the mapping. + + .. versionchanged:: 1.7 + Callables are now supported for custom bucket types. + """ + + def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + if isinstance(func, Command): + func._buckets = CooldownMapping(Cooldown(rate, per), type) + else: + func.__commands_cooldown__ = CooldownMapping(Cooldown(rate, per), type) + return func + return decorator # type: ignore + +def dynamic_cooldown(cooldown: Union[BucketType, Callable[[Message], Any]], type: BucketType = BucketType.default) -> Callable[[T], T]: + """A decorator that adds a dynamic cooldown to a :class:`.Command` + + This differs from :func:`.cooldown` in that it takes a function that + accepts a single parameter of type :class:`.discord.Message` and must + return a :class:`.Cooldown` or ``None``. If ``None`` is returned then + that cooldown is effectively bypassed. + + A cooldown allows a command to only be used a specific amount + of times in a specific time frame. These cooldowns can be based + either on a per-guild, per-channel, per-user, per-role or global basis. + Denoted by the third argument of ``type`` which must be of enum + type :class:`.BucketType`. + + If a cooldown is triggered, then :exc:`.CommandOnCooldown` is triggered in + :func:`.on_command_error` and the local error handler. + + A command can only have a single cooldown. + + .. versionadded:: 2.0 + + Parameters + ------------ + cooldown: Callable[[:class:`.discord.Message`], Optional[:class:`.Cooldown`]] + A function that takes a message and returns a cooldown that will + apply to this invocation or ``None`` if the cooldown should be bypassed. + type: :class:`.BucketType` + The type of cooldown to have. + """ + if not callable(cooldown): + raise TypeError("A callable must be provided") + + def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + if isinstance(func, Command): + func._buckets = DynamicCooldownMapping(cooldown, type) + else: + func.__commands_cooldown__ = DynamicCooldownMapping(cooldown, type) + return func + return decorator # type: ignore + +def max_concurrency(number: int, per: BucketType = BucketType.default, *, wait: bool = False) -> Callable[[T], T]: + """A decorator that adds a maximum concurrency to a :class:`.Command` or its subclasses. + + This enables you to only allow a certain number of command invocations at the same time, + for example if a command takes too long or if only one user can use it at a time. This + differs from a cooldown in that there is no set waiting period or token bucket -- only + a set number of people can run the command. + + .. versionadded:: 1.3 + + Parameters + ------------- + number: :class:`int` + The maximum number of invocations of this command that can be running at the same time. + per: :class:`.BucketType` + The bucket that this concurrency is based on, e.g. ``BucketType.guild`` would allow + it to be used up to ``number`` times per guild. + wait: :class:`bool` + Whether the command should wait for the queue to be over. If this is set to ``False`` + then instead of waiting until the command can run again, the command raises + :exc:`.MaxConcurrencyReached` to its error handler. If this is set to ``True`` + then the command waits until it can be executed. + """ + + def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: + value = MaxConcurrency(number, per=per, wait=wait) + if isinstance(func, Command): + func._max_concurrency = value + else: + func.__commands_max_concurrency__ = value + return func + return decorator # type: ignore + +def before_invoke(coro) -> Callable[[T], T]: + """A decorator that registers a coroutine as a pre-invoke hook. + + This allows you to refer to one before invoke hook for several commands that + do not have to be within the same cog. + + .. versionadded:: 1.4 + + Example + --------- + + .. code-block:: python3 + + async def record_usage(ctx): + print(ctx.author, 'used', ctx.command, 'at', ctx.message.created_at) + + @bot.command() + @commands.before_invoke(record_usage) + async def who(ctx): # Output: used who at