Repository: Pycord-Development/pycord

Issue:
Interaction responses don't respect client proxy settings
### Summary

Bot.client(proxy="xx") not works for ctx.respond

### Reproduction Steps

I ran my code in a limited network, unless it passes through proxy or discord api requests will be blocked 
Code as follows, only ctx.send passed through proxy

```Python
bot = discord.Bot(
    # trust_env=True,
    proxy="http://localhost:9999",
)

@bot.slash_command(guild_ids=["my guild id"])
async def hello2(ctx):
    """Say hello to the bot"""  # the command description can be supplied as the docstring
    await ctx.send(f"Hello {ctx.author}!")
    await ctx.respond(f"Hello {ctx.author}!")
```

![image](https://user-images.githubusercontent.com/10193482/148029361-abf38af5-de8d-4ddb-8725-0a78dcd5f086.png)

### Minimal Reproducible Code

_No response_

### Expected Results

all ctx.send he ctx.respond pass network request through proxy

### Actual Results

only ctx.send passed through proxy

### Intents

discord.Intents.default()

### System Information

- Python v3.10.1-final
- py-cord v2.0.0-alpha
    - py-cord pkg_resources: v2.0.0a4738+g99e2312f
- aiohttp v3.7.4.post0
- system info: Darwin 20.6.0 Darwin Kernel Version 20.6.0: Wed Nov 10 22:23:07 PST 2021; root:xnu-7195.141.14~1/RELEASE_X86_64

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Addtional Content
I had looked at the source, the reason is `ctx.respond` uses `AsyncWebhookAdapter.request` to send api.

here are codes of AsyncWebhookAdapter.request, I found session.request don't use proxy
```Python
class AsyncWebhookAdapter:
    def __init__(self):
        self._locks: weakref.WeakValueDictionary = weakref.WeakValueDictionary()

    async def request(...):
        ...
        async with session.request(method, url, data=to_send, headers=headers, params=params) as response:
              ....
```

_No response_


Discussion:
Proxies don't seem to be supported by the webhook adapter, I'm updating this issue to be a feature request.

Resolving patch:
diff --git a/discord/interactions.py b/discord/interactions.py
--- a/discord/interactions.py
+++ b/discord/interactions.py
@@ -296,10 +296,13 @@ async def original_message(self) -> InteractionMessage:
             raise ClientException("Channel for message could not be resolved")
 
         adapter = async_context.get()
+        http = self._state.http
         data = await adapter.get_original_interaction_response(
             application_id=self.application_id,
             token=self.token,
             session=self._session,
+            proxy=http.proxy,
+            proxy_auth=http.proxy_auth,
         )
         state = _InteractionMessageState(self, self._state)
         message = InteractionMessage(state=state, channel=channel, data=data)  # type: ignore
@@ -387,10 +390,13 @@ async def edit_original_message(
             previous_allowed_mentions=previous_mentions,
         )
         adapter = async_context.get()
+        http = self._state.http
         data = await adapter.edit_original_interaction_response(
             self.application_id,
             self.token,
             session=self._session,
+            proxy=http.proxy,
+            proxy_auth=http.proxy_auth,
             payload=params.payload,
             multipart=params.multipart,
             files=params.files,
@@ -429,10 +435,13 @@ async def delete_original_message(self, *, delay: Optional[float] = None) -> Non
             Deleted a message that is not yours.
         """
         adapter = async_context.get()
+        http = self._state.http
         func = adapter.delete_original_interaction_response(
             self.application_id,
             self.token,
             session=self._session,
+            proxy=http.proxy,
+            proxy_auth=http.proxy_auth,
         )
 
         if delay is not None:
@@ -566,6 +575,7 @@ async def defer(self, *, ephemeral: bool = False, invisible: bool = True) -> Non
 
         if defer_type:
             adapter = async_context.get()
+            http = parent._state.http
             await self._locked_response(
                 adapter.create_interaction_response(
                     parent.id,
@@ -573,6 +583,8 @@ async def defer(self, *, ephemeral: bool = False, invisible: bool = True) -> Non
                     session=parent._session,
                     type=defer_type,
                     data=data,
+                    proxy=http.proxy,
+                    proxy_auth=http.proxy_auth,
                 )
             )
             self._responded = True
@@ -597,11 +609,14 @@ async def pong(self) -> None:
         parent = self._parent
         if parent.type is InteractionType.ping:
             adapter = async_context.get()
+            http = parent._state.http
             await self._locked_response(
                 adapter.create_interaction_response(
                     parent.id,
                     parent.token,
                     session=parent._session,
+                    proxy=http.proxy,
+                    proxy_auth=http.proxy_auth,
                     type=InteractionResponseType.pong.value,
                 )
             )
@@ -723,6 +738,7 @@ async def send_message(
 
         parent = self._parent
         adapter = async_context.get()
+        http = parent._state.http
         try:
             await self._locked_response(
                 adapter.create_interaction_response(
@@ -730,6 +746,8 @@ async def send_message(
                     parent.token,
                     session=parent._session,
                     type=InteractionResponseType.channel_message.value,
+                    proxy=http.proxy,
+                    proxy_auth=http.proxy_auth,
                     data=payload,
                     files=files,
                 )
@@ -852,6 +870,7 @@ async def edit_message(
                 payload["attachments"] = [a.to_dict() for a in msg.attachments]
 
         adapter = async_context.get()
+        http = parent._state.http
         try:
             await self._locked_response(
                 adapter.create_interaction_response(
@@ -859,6 +878,8 @@ async def edit_message(
                     parent.token,
                     session=parent._session,
                     type=InteractionResponseType.message_update.value,
+                    proxy=http.proxy,
+                    proxy_auth=http.proxy_auth,
                     data=payload,
                     files=files,
                 )
@@ -906,11 +927,14 @@ async def send_autocomplete_result(
         payload = {"choices": [c.to_dict() for c in choices]}
 
         adapter = async_context.get()
+        http = parent._state.http
         await self._locked_response(
             adapter.create_interaction_response(
                 parent.id,
                 parent.token,
                 session=parent._session,
+                proxy=http.proxy,
+                proxy_auth=http.proxy_auth,
                 type=InteractionResponseType.auto_complete_result.value,
                 data=payload,
             )
@@ -938,13 +962,18 @@ async def send_modal(self, modal: Modal) -> Interaction:
         if self._responded:
             raise InteractionResponded(self._parent)
 
+        parent = self._parent
+
         payload = modal.to_dict()
         adapter = async_context.get()
+        http = parent._state.http
         await self._locked_response(
             adapter.create_interaction_response(
-                self._parent.id,
-                self._parent.token,
-                session=self._parent._session,
+                parent.id,
+                parent.token,
+                session=parent._session,
+                proxy=http.proxy,
+                proxy_auth=http.proxy_auth,
                 type=InteractionResponseType.modal.value,
                 data=payload,
             )
diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py
--- a/discord/webhook/async_.py
+++ b/discord/webhook/async_.py
@@ -121,6 +121,8 @@ async def request(
         *,
         payload: Optional[Dict[str, Any]] = None,
         multipart: Optional[List[Dict[str, Any]]] = None,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         files: Optional[List[File]] = None,
         reason: Optional[str] = None,
         auth_token: Optional[str] = None,
@@ -163,7 +165,15 @@ async def request(
                         form_data.add_field(**p)
                     to_send = form_data
                 try:
-                    async with session.request(method, url, data=to_send, headers=headers, params=params) as response:
+                    async with session.request(
+                            method,
+                            url,
+                            data=to_send,
+                            headers=headers,
+                            params=params,
+                            proxy=proxy,
+                            proxy_auth=proxy_auth,
+                    ) as response:
                         _log.debug(
                             "Webhook ID %s with %s %s has returned status code %s",
                             webhook_id,
@@ -231,10 +241,12 @@ def delete_webhook(
         *,
         token: Optional[str] = None,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         reason: Optional[str] = None,
     ) -> Response[None]:
         route = Route("DELETE", "/webhooks/{webhook_id}", webhook_id=webhook_id)
-        return self.request(route, session, reason=reason, auth_token=token)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason, auth_token=token)
 
     def delete_webhook_with_token(
         self,
@@ -242,6 +254,8 @@ def delete_webhook_with_token(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         reason: Optional[str] = None,
     ) -> Response[None]:
         route = Route(
@@ -250,7 +264,7 @@ def delete_webhook_with_token(
             webhook_id=webhook_id,
             webhook_token=token,
         )
-        return self.request(route, session, reason=reason)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason)
 
     def edit_webhook(
         self,
@@ -259,10 +273,14 @@ def edit_webhook(
         payload: Dict[str, Any],
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         reason: Optional[str] = None,
     ) -> Response[WebhookPayload]:
         route = Route("PATCH", "/webhooks/{webhook_id}", webhook_id=webhook_id)
-        return self.request(route, session, reason=reason, payload=payload, auth_token=token)
+        return self.request(
+            route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason, payload=payload, auth_token=token
+        )
 
     def edit_webhook_with_token(
         self,
@@ -271,6 +289,8 @@ def edit_webhook_with_token(
         payload: Dict[str, Any],
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         reason: Optional[str] = None,
     ) -> Response[WebhookPayload]:
         route = Route(
@@ -279,7 +299,7 @@ def edit_webhook_with_token(
             webhook_id=webhook_id,
             webhook_token=token,
         )
-        return self.request(route, session, reason=reason, payload=payload)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, reason=reason, payload=payload)
 
     def execute_webhook(
         self,
@@ -287,6 +307,8 @@ def execute_webhook(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         payload: Optional[Dict[str, Any]] = None,
         multipart: Optional[List[Dict[str, Any]]] = None,
         files: Optional[List[File]] = None,
@@ -311,6 +333,8 @@ def execute_webhook(
             route,
             session,
             payload=payload,
+            proxy=proxy,
+            proxy_auth=proxy_auth,
             multipart=multipart,
             files=files,
             params=params,
@@ -323,6 +347,8 @@ def get_webhook_message(
         message_id: int,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         thread_id: Optional[int] = None,
     ) -> Response[MessagePayload]:
         params = {}
@@ -337,7 +363,7 @@ def get_webhook_message(
             webhook_token=token,
             message_id=message_id,
         )
-        return self.request(route, session, params=params)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, params=params)
 
     def edit_webhook_message(
         self,
@@ -346,6 +372,8 @@ def edit_webhook_message(
         message_id: int,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         thread_id: Optional[int] = None,
         payload: Optional[Dict[str, Any]] = None,
         multipart: Optional[List[Dict[str, Any]]] = None,
@@ -368,6 +396,8 @@ def edit_webhook_message(
             session,
             params=params,
             payload=payload,
+            proxy=proxy,
+            proxy_auth=proxy_auth,
             multipart=multipart,
             files=files,
         )
@@ -379,6 +409,8 @@ def delete_webhook_message(
         message_id: int,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         thread_id: Optional[int] = None,
     ) -> Response[None]:
         params = {}
@@ -393,7 +425,7 @@ def delete_webhook_message(
             webhook_token=token,
             message_id=message_id,
         )
-        return self.request(route, session, params=params)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, params=params)
 
     def fetch_webhook(
         self,
@@ -401,9 +433,11 @@ def fetch_webhook(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
     ) -> Response[WebhookPayload]:
         route = Route("GET", "/webhooks/{webhook_id}", webhook_id=webhook_id)
-        return self.request(route, session=session, auth_token=token)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, auth_token=token)
 
     def fetch_webhook_with_token(
         self,
@@ -411,6 +445,8 @@ def fetch_webhook_with_token(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
     ) -> Response[WebhookPayload]:
         route = Route(
             "GET",
@@ -418,7 +454,7 @@ def fetch_webhook_with_token(
             webhook_id=webhook_id,
             webhook_token=token,
         )
-        return self.request(route, session=session)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth)
 
     def create_interaction_response(
         self,
@@ -426,6 +462,8 @@ def create_interaction_response(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         type: int,
         data: Optional[Dict[str, Any]] = None,
         files: List[File] = None,
@@ -465,7 +503,7 @@ def create_interaction_response(
             webhook_token=token,
         )
 
-        return self.request(route, session=session, files=files, multipart=form)
+        return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, files=files, multipart=form)
 
     def get_original_interaction_response(
         self,
@@ -473,6 +511,8 @@ def get_original_interaction_response(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
     ) -> Response[MessagePayload]:
         r = Route(
             "GET",
@@ -480,7 +520,7 @@ def get_original_interaction_response(
             webhook_id=application_id,
             webhook_token=token,
         )
-        return self.request(r, session=session)
+        return self.request(r, session=session, proxy=proxy, proxy_auth=proxy_auth)
 
     def edit_original_interaction_response(
         self,
@@ -488,6 +528,8 @@ def edit_original_interaction_response(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         payload: Optional[Dict[str, Any]] = None,
         multipart: Optional[List[Dict[str, Any]]] = None,
         files: Optional[List[File]] = None,
@@ -498,7 +540,9 @@ def edit_original_interaction_response(
             webhook_id=application_id,
             webhook_token=token,
         )
-        return self.request(r, session, payload=payload, multipart=multipart, files=files)
+        return self.request(
+            r, session=session, proxy=proxy, proxy_auth=proxy_auth, payload=payload, multipart=multipart, files=files
+        )
 
     def delete_original_interaction_response(
         self,
@@ -506,6 +550,8 @@ def delete_original_interaction_response(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
     ) -> Response[None]:
         r = Route(
             "DELETE",
@@ -513,7 +559,7 @@ def delete_original_interaction_response(
             webhook_id=application_id,
             webhook_token=token,
         )
-        return self.request(r, session=session)
+        return self.request(r, session=session, proxy=proxy, proxy_auth=proxy_auth)
 
 
 class ExecuteWebhookParameters(NamedTuple):
@@ -1040,17 +1086,21 @@ async def foo():
         .. versionadded:: 2.0
     """
 
-    __slots__: Tuple[str, ...] = ("session",)
+    __slots__: Tuple[str, ...] = ("session", "proxy", "proxy_auth")
 
     def __init__(
         self,
         data: WebhookPayload,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         token: Optional[str] = None,
         state=None,
     ):
         super().__init__(data, token, state)
         self.session = session
+        self.proxy: Optional[str] = proxy
+        self.proxy_auth: Optional[aiohttp.BasicAuth] = proxy_auth
 
     def __repr__(self):
         return f"<Webhook id={self.id!r}>"
@@ -1067,6 +1117,8 @@ def partial(
         token: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         bot_token: Optional[str] = None,
     ) -> Webhook:
         """Creates a partial :class:`Webhook`.
@@ -1101,7 +1153,7 @@ def partial(
             "token": token,
         }
 
-        return cls(data, session, token=bot_token)
+        return cls(data, session, proxy=proxy, proxy_auth=proxy_auth, token=bot_token)
 
     @classmethod
     def from_url(
@@ -1109,6 +1161,8 @@ def from_url(
         url: str,
         *,
         session: aiohttp.ClientSession,
+        proxy: Optional[str] = None,
+        proxy_auth: Optional[aiohttp.BasicAuth] = None,
         bot_token: Optional[str] = None,
     ) -> Webhook:
         """Creates a partial :class:`Webhook` from a webhook URL.
@@ -1149,7 +1203,7 @@ def from_url(
 
         data: Dict[str, Any] = m.groupdict()
         data["type"] = 1
-        return cls(data, session, token=bot_token)  # type: ignore
+        return cls(data, session, proxy=proxy, proxy_auth=proxy_auth, token=bot_token)  # type: ignore
 
     @classmethod
     def _as_follower(cls, data, *, channel, user) -> Webhook:
@@ -1169,13 +1223,19 @@ def _as_follower(cls, data, *, channel, user) -> Webhook:
         }
 
         state = channel._state
-        session = channel._state.http._HTTPClient__session
-        return cls(feed, session=session, state=state, token=state.http.token)
+        http = state.http
+        session = http._HTTPClient__session
+        proxy_auth = http.proxy_auth
+        proxy = http.proxy
+        return cls(feed, session=session, state=state, proxy_auth=proxy_auth, proxy=proxy, token=state.http.token)
 
     @classmethod
     def from_state(cls, data, state) -> Webhook:
-        session = state.http._HTTPClient__session
-        return cls(data, session=session, state=state, token=state.http.token)
+        http = state.http
+        session = http._HTTPClient__session
+        proxy_auth = http.proxy_auth
+        proxy = http.proxy
+        return cls(data, session=session, state=state, proxy_auth=proxy_auth, proxy=proxy, token=state.http.token)
 
     async def fetch(self, *, prefer_auth: bool = True) -> Webhook:
         """|coro|
@@ -1215,13 +1275,32 @@ async def fetch(self, *, prefer_auth: bool = True) -> Webhook:
         adapter = async_context.get()
 
         if prefer_auth and self.auth_token:
-            data = await adapter.fetch_webhook(self.id, self.auth_token, session=self.session)
+            data = await adapter.fetch_webhook(
+                self.id,
+                self.auth_token,
+                session=self.session,
+                proxy=self.proxy,
+                proxy_auth=self.proxy_auth,
+            )
         elif self.token:
-            data = await adapter.fetch_webhook_with_token(self.id, self.token, session=self.session)
+            data = await adapter.fetch_webhook_with_token(
+                self.id,
+                self.token,
+                session=self.session,
+                proxy=self.proxy,
+                proxy_auth=self.proxy_auth,
+            )
         else:
             raise InvalidArgument("This webhook does not have a token associated with it")
 
-        return Webhook(data, self.session, token=self.auth_token, state=self._state)
+        return Webhook(
+            data,
+            session=self.session,
+            proxy=self.proxy,
+            proxy_auth=self.proxy_auth,
+            token=self.auth_token,
+            state=self._state,
+        )
 
     async def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True):
         """|coro|
@@ -1257,9 +1336,18 @@ async def delete(self, *, reason: Optional[str] = None, prefer_auth: bool = True
         adapter = async_context.get()
 
         if prefer_auth and self.auth_token:
-            await adapter.delete_webhook(self.id, token=self.auth_token, session=self.session, reason=reason)
+            await adapter.delete_webhook(
+                self.id,
+                token=self.auth_token,
+                session=self.session,
+                proxy=self.proxy,
+                proxy_auth=self.proxy_auth,
+                reason=reason,
+            )
         elif self.token:
-            await adapter.delete_webhook_with_token(self.id, self.token, session=self.session, reason=reason)
+            await adapter.delete_webhook_with_token(
+                self.id, self.token, session=self.session, proxy=self.proxy, proxy_auth=self.proxy_auth, reason=reason,
+            )
 
     async def edit(
         self,
@@ -1328,6 +1416,8 @@ async def edit(
                 self.auth_token,
                 payload=payload,
                 session=self.session,
+                proxy=self.proxy,
+                proxy_auth=self.proxy_auth,
                 reason=reason,
             )
 
@@ -1337,6 +1427,8 @@ async def edit(
                 self.auth_token,
                 payload=payload,
                 session=self.session,
+                proxy=self.proxy,
+                proxy_auth=self.proxy_auth,
                 reason=reason,
             )
         elif self.token:
@@ -1345,13 +1437,22 @@ async def edit(
                 self.token,
                 payload=payload,
                 session=self.session,
+                proxy=self.proxy,
+                proxy_auth=self.proxy_auth,
                 reason=reason,
             )
 
         if data is None:
             raise RuntimeError("Unreachable code hit: data was not assigned")
 
-        return Webhook(data=data, session=self.session, token=self.auth_token, state=self._state)
+        return Webhook(
+            data,
+            session=self.session,
+            proxy=self.proxy,
+            proxy_auth=self.proxy_auth,
+            token=self.auth_token,
+            state=self._state,
+        )
 
     def _create_message(self, data):
         state = _WebhookState(self, parent=self._state)
@@ -1562,6 +1663,8 @@ async def send(
             self.id,
             self.token,
             session=self.session,
+            proxy=self.proxy,
+            proxy_auth=self.proxy_auth,
             payload=params.payload,
             multipart=params.multipart,
             files=params.files,
@@ -1628,6 +1731,8 @@ async def fetch_message(self, id: int, *, thread_id: Optional[int] = None) -> We
             self.token,
             id,
             session=self.session,
+            proxy=self.proxy,
+            proxy_auth=self.proxy_auth,
             thread_id=thread_id,
         )
         msg = self._create_message(data)
@@ -1749,6 +1854,8 @@ async def edit_message(
             self.token,
             message_id,
             session=self.session,
+            proxy=self.proxy,
+            proxy_auth=self.proxy_auth,
             thread_id=thread_id,
             payload=params.payload,
             multipart=params.multipart,
@@ -1793,5 +1900,7 @@ async def delete_message(self, message_id: int, *, thread_id: Optional[int] = No
             self.token,
             message_id,
             session=self.session,
+            proxy=self.proxy,
+            proxy_auth=self.proxy_auth,
             thread_id=thread_id,
         )


Test patch: