disagreement 0.1.0rc2__py3-none-any.whl → 0.2.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
disagreement/http.py CHANGED
@@ -5,6 +5,7 @@ HTTP client for interacting with the Discord REST API.
5
5
  """
6
6
 
7
7
  import asyncio
8
+ import logging
8
9
  import aiohttp # pylint: disable=import-error
9
10
  import json
10
11
  from urllib.parse import quote
@@ -22,12 +23,14 @@ from .interactions import InteractionResponsePayload
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from .client import Client
25
- from .models import Message, Webhook, File
26
+ from .models import Message, Webhook, File, StageInstance, Invite
26
27
  from .interactions import ApplicationCommand, Snowflake
27
28
 
28
29
  # Discord API constants
29
30
  API_BASE_URL = "https://discord.com/api/v10" # Using API v10
30
31
 
32
+ logger = logging.getLogger(__name__)
33
+
31
34
 
32
35
  class HTTPClient:
33
36
  """Handles HTTP requests to the Discord API."""
@@ -37,12 +40,27 @@ class HTTPClient:
37
40
  token: str,
38
41
  client_session: Optional[aiohttp.ClientSession] = None,
39
42
  verbose: bool = False,
43
+ **session_kwargs: Any,
40
44
  ):
45
+ """Create a new HTTP client.
46
+
47
+ Parameters
48
+ ----------
49
+ token:
50
+ Bot token for authentication.
51
+ client_session:
52
+ Optional existing :class:`aiohttp.ClientSession`.
53
+ verbose:
54
+ If ``True``, log HTTP requests and responses.
55
+ **session_kwargs:
56
+ Additional options forwarded to :class:`aiohttp.ClientSession`, such
57
+ as ``proxy`` or ``connector``.
58
+ """
59
+
41
60
  self.token = token
42
- self._session: Optional[aiohttp.ClientSession] = (
43
- client_session # Can be externally managed
44
- )
45
- self.user_agent = f"DiscordBot (https://github.com/yourusername/disagreement, {__version__})" # Customize URL
61
+ self._session: Optional[aiohttp.ClientSession] = client_session
62
+ self._session_kwargs: Dict[str, Any] = session_kwargs
63
+ self.user_agent = f"DiscordBot (https://github.com/Slipstreamm/disagreement, {__version__})" # Customize URL
46
64
 
47
65
  self.verbose = verbose
48
66
 
@@ -50,7 +68,7 @@ class HTTPClient:
50
68
 
51
69
  async def _ensure_session(self):
52
70
  if self._session is None or self._session.closed:
53
- self._session = aiohttp.ClientSession()
71
+ self._session = aiohttp.ClientSession(**self._session_kwargs)
54
72
 
55
73
  async def close(self):
56
74
  """Closes the underlying aiohttp.ClientSession."""
@@ -86,7 +104,13 @@ class HTTPClient:
86
104
  final_headers.update(custom_headers)
87
105
 
88
106
  if self.verbose:
89
- print(f"HTTP REQUEST: {method} {url} | payload={payload} params={params}")
107
+ logger.debug(
108
+ "HTTP REQUEST: %s %s | payload=%s params=%s",
109
+ method,
110
+ url,
111
+ payload,
112
+ params,
113
+ )
90
114
 
91
115
  route = f"{method.upper()}:{endpoint}"
92
116
 
@@ -119,7 +143,9 @@ class HTTPClient:
119
143
  ) # Fallback to text if JSON parsing fails
120
144
 
121
145
  if self.verbose:
122
- print(f"HTTP RESPONSE: {response.status} {url} | {data}")
146
+ logger.debug(
147
+ "HTTP RESPONSE: %s %s | %s", response.status, url, data
148
+ )
123
149
 
124
150
  self._rate_limiter.release(route, response.headers)
125
151
 
@@ -150,8 +176,12 @@ class HTTPClient:
150
176
  )
151
177
 
152
178
  if attempt < 4: # Don't log on the last attempt before raising
153
- print(
154
- f"{error_message} Retrying after {retry_after}s (Attempt {attempt + 1}/5). Global: {is_global}"
179
+ logger.warning(
180
+ "%s Retrying after %ss (Attempt %s/5). Global: %s",
181
+ error_message,
182
+ retry_after,
183
+ attempt + 1,
184
+ is_global,
155
185
  )
156
186
  continue # Retry the request
157
187
  else: # Last attempt failed
@@ -394,6 +424,30 @@ class HTTPClient:
394
424
  """Fetches a channel by ID."""
395
425
  return await self.request("GET", f"/channels/{channel_id}")
396
426
 
427
+ async def get_channel_invites(
428
+ self, channel_id: "Snowflake"
429
+ ) -> List[Dict[str, Any]]:
430
+ """Fetches the invites for a channel."""
431
+
432
+ return await self.request("GET", f"/channels/{channel_id}/invites")
433
+
434
+ async def create_invite(
435
+ self, channel_id: "Snowflake", payload: Dict[str, Any]
436
+ ) -> "Invite":
437
+ """Creates an invite for a channel."""
438
+
439
+ data = await self.request(
440
+ "POST", f"/channels/{channel_id}/invites", payload=payload
441
+ )
442
+ from .models import Invite
443
+
444
+ return Invite.from_dict(data)
445
+
446
+ async def delete_invite(self, code: str) -> None:
447
+ """Deletes an invite by code."""
448
+
449
+ await self.request("DELETE", f"/invites/{code}")
450
+
397
451
  async def create_webhook(
398
452
  self, channel_id: "Snowflake", payload: Dict[str, Any]
399
453
  ) -> "Webhook":
@@ -574,6 +628,87 @@ class HTTPClient:
574
628
  """Fetches a guild object for a given guild ID."""
575
629
  return await self.request("GET", f"/guilds/{guild_id}")
576
630
 
631
+ async def get_guild_templates(self, guild_id: "Snowflake") -> List[Dict[str, Any]]:
632
+ """Fetches all templates for the given guild."""
633
+ return await self.request("GET", f"/guilds/{guild_id}/templates")
634
+
635
+ async def create_guild_template(
636
+ self, guild_id: "Snowflake", payload: Dict[str, Any]
637
+ ) -> Dict[str, Any]:
638
+ """Creates a guild template."""
639
+ return await self.request(
640
+ "POST", f"/guilds/{guild_id}/templates", payload=payload
641
+ )
642
+
643
+ async def sync_guild_template(
644
+ self, guild_id: "Snowflake", template_code: str
645
+ ) -> Dict[str, Any]:
646
+ """Syncs a guild template to the guild's current state."""
647
+ return await self.request(
648
+ "PUT",
649
+ f"/guilds/{guild_id}/templates/{template_code}",
650
+ )
651
+
652
+ async def delete_guild_template(
653
+ self, guild_id: "Snowflake", template_code: str
654
+ ) -> None:
655
+ """Deletes a guild template."""
656
+ await self.request("DELETE", f"/guilds/{guild_id}/templates/{template_code}")
657
+
658
+ async def get_guild_scheduled_events(
659
+ self, guild_id: "Snowflake"
660
+ ) -> List[Dict[str, Any]]:
661
+ """Returns a list of scheduled events for the guild."""
662
+
663
+ return await self.request("GET", f"/guilds/{guild_id}/scheduled-events")
664
+
665
+ async def get_guild_scheduled_event(
666
+ self, guild_id: "Snowflake", event_id: "Snowflake"
667
+ ) -> Dict[str, Any]:
668
+ """Returns a guild scheduled event."""
669
+
670
+ return await self.request(
671
+ "GET", f"/guilds/{guild_id}/scheduled-events/{event_id}"
672
+ )
673
+
674
+ async def create_guild_scheduled_event(
675
+ self, guild_id: "Snowflake", payload: Dict[str, Any]
676
+ ) -> Dict[str, Any]:
677
+ """Creates a guild scheduled event."""
678
+
679
+ return await self.request(
680
+ "POST", f"/guilds/{guild_id}/scheduled-events", payload=payload
681
+ )
682
+
683
+ async def edit_guild_scheduled_event(
684
+ self, guild_id: "Snowflake", event_id: "Snowflake", payload: Dict[str, Any]
685
+ ) -> Dict[str, Any]:
686
+ """Edits a guild scheduled event."""
687
+
688
+ return await self.request(
689
+ "PATCH",
690
+ f"/guilds/{guild_id}/scheduled-events/{event_id}",
691
+ payload=payload,
692
+ )
693
+
694
+ async def delete_guild_scheduled_event(
695
+ self, guild_id: "Snowflake", event_id: "Snowflake"
696
+ ) -> None:
697
+ """Deletes a guild scheduled event."""
698
+
699
+ await self.request("DELETE", f"/guilds/{guild_id}/scheduled-events/{event_id}")
700
+
701
+ async def get_audit_logs(
702
+ self, guild_id: "Snowflake", **filters: Any
703
+ ) -> Dict[str, Any]:
704
+ """Fetches audit log entries for a guild."""
705
+ params = {k: v for k, v in filters.items() if v is not None}
706
+ return await self.request(
707
+ "GET",
708
+ f"/guilds/{guild_id}/audit-logs",
709
+ params=params if params else None,
710
+ )
711
+
577
712
  # Add other methods like:
578
713
  # async def get_guild(self, guild_id: str) -> Dict[str, Any]: ...
579
714
  # async def create_reaction(self, channel_id: str, message_id: str, emoji: str) -> None: ...
@@ -858,3 +993,49 @@ class HTTPClient:
858
993
  async def trigger_typing(self, channel_id: str) -> None:
859
994
  """Sends a typing indicator to the specified channel."""
860
995
  await self.request("POST", f"/channels/{channel_id}/typing")
996
+
997
+ async def start_stage_instance(
998
+ self, payload: Dict[str, Any], reason: Optional[str] = None
999
+ ) -> "StageInstance":
1000
+ """Starts a stage instance."""
1001
+
1002
+ headers = {"X-Audit-Log-Reason": reason} if reason else None
1003
+ data = await self.request(
1004
+ "POST", "/stage-instances", payload=payload, custom_headers=headers
1005
+ )
1006
+ from .models import StageInstance
1007
+
1008
+ return StageInstance(data)
1009
+
1010
+ async def edit_stage_instance(
1011
+ self,
1012
+ channel_id: "Snowflake",
1013
+ payload: Dict[str, Any],
1014
+ reason: Optional[str] = None,
1015
+ ) -> "StageInstance":
1016
+ """Edits an existing stage instance."""
1017
+
1018
+ headers = {"X-Audit-Log-Reason": reason} if reason else None
1019
+ data = await self.request(
1020
+ "PATCH",
1021
+ f"/stage-instances/{channel_id}",
1022
+ payload=payload,
1023
+ custom_headers=headers,
1024
+ )
1025
+ from .models import StageInstance
1026
+
1027
+ return StageInstance(data)
1028
+
1029
+ async def end_stage_instance(
1030
+ self, channel_id: "Snowflake", reason: Optional[str] = None
1031
+ ) -> None:
1032
+ """Ends a stage instance."""
1033
+
1034
+ headers = {"X-Audit-Log-Reason": reason} if reason else None
1035
+ await self.request(
1036
+ "DELETE", f"/stage-instances/{channel_id}", custom_headers=headers
1037
+ )
1038
+
1039
+ async def get_voice_regions(self) -> List[Dict[str, Any]]:
1040
+ """Returns available voice regions."""
1041
+ return await self.request("GET", "/voice/regions")
disagreement/models.py CHANGED
@@ -4,12 +4,13 @@
4
4
  Data models for Discord objects.
5
5
  """
6
6
 
7
- import json
8
- import asyncio
9
- import aiohttp # pylint: disable=import-error
10
7
  import asyncio
8
+ import json
9
+ from dataclasses import dataclass
11
10
  from typing import Any, AsyncIterator, Dict, List, Optional, TYPE_CHECKING, Union
12
11
 
12
+ import aiohttp # pylint: disable=import-error
13
+ from .color import Color
13
14
  from .errors import DisagreementException, HTTPException
14
15
  from .enums import ( # These enums will need to be defined in disagreement/enums.py
15
16
  VerificationLevel,
@@ -22,10 +23,12 @@ from .enums import ( # These enums will need to be defined in disagreement/enum
22
23
  ChannelType,
23
24
  ComponentType,
24
25
  ButtonStyle, # Added for Button
26
+ GuildScheduledEventPrivacyLevel,
27
+ GuildScheduledEventStatus,
28
+ GuildScheduledEventEntityType,
25
29
  # SelectMenuType will be part of ComponentType or a new enum if needed
26
30
  )
27
31
  from .permissions import Permissions
28
- from .color import Color
29
32
 
30
33
 
31
34
  if TYPE_CHECKING:
@@ -1087,7 +1090,6 @@ class TextChannel(Channel):
1087
1090
  )
1088
1091
  self.last_pin_timestamp: Optional[str] = data.get("last_pin_timestamp")
1089
1092
 
1090
-
1091
1093
  def history(
1092
1094
  self,
1093
1095
  *,
@@ -1143,7 +1145,6 @@ class TextChannel(Channel):
1143
1145
  self._client._messages.pop(mid, None)
1144
1146
  return ids
1145
1147
 
1146
-
1147
1148
  def __repr__(self) -> str:
1148
1149
  return f"<TextChannel id='{self.id}' name='{self.name}' guild_id='{self.guild_id}'>"
1149
1150
 
@@ -1162,6 +1163,85 @@ class VoiceChannel(Channel):
1162
1163
  return f"<VoiceChannel id='{self.id}' name='{self.name}' guild_id='{self.guild_id}'>"
1163
1164
 
1164
1165
 
1166
+ class StageChannel(VoiceChannel):
1167
+ """Represents a guild stage channel."""
1168
+
1169
+ def __repr__(self) -> str:
1170
+ return f"<StageChannel id='{self.id}' name='{self.name}' guild_id='{self.guild_id}'>"
1171
+
1172
+ async def start_stage_instance(
1173
+ self,
1174
+ topic: str,
1175
+ *,
1176
+ privacy_level: int = 2,
1177
+ reason: Optional[str] = None,
1178
+ guild_scheduled_event_id: Optional[str] = None,
1179
+ ) -> "StageInstance":
1180
+ if not hasattr(self._client, "_http"):
1181
+ raise DisagreementException("Client missing HTTP for stage instance")
1182
+
1183
+ payload: Dict[str, Any] = {
1184
+ "channel_id": self.id,
1185
+ "topic": topic,
1186
+ "privacy_level": privacy_level,
1187
+ }
1188
+ if guild_scheduled_event_id is not None:
1189
+ payload["guild_scheduled_event_id"] = guild_scheduled_event_id
1190
+
1191
+ instance = await self._client._http.start_stage_instance(payload, reason=reason)
1192
+ instance._client = self._client
1193
+ return instance
1194
+
1195
+ async def edit_stage_instance(
1196
+ self,
1197
+ *,
1198
+ topic: Optional[str] = None,
1199
+ privacy_level: Optional[int] = None,
1200
+ reason: Optional[str] = None,
1201
+ ) -> "StageInstance":
1202
+ if not hasattr(self._client, "_http"):
1203
+ raise DisagreementException("Client missing HTTP for stage instance")
1204
+
1205
+ payload: Dict[str, Any] = {}
1206
+ if topic is not None:
1207
+ payload["topic"] = topic
1208
+ if privacy_level is not None:
1209
+ payload["privacy_level"] = privacy_level
1210
+
1211
+ instance = await self._client._http.edit_stage_instance(
1212
+ self.id, payload, reason=reason
1213
+ )
1214
+ instance._client = self._client
1215
+ return instance
1216
+
1217
+ async def end_stage_instance(self, *, reason: Optional[str] = None) -> None:
1218
+ if not hasattr(self._client, "_http"):
1219
+ raise DisagreementException("Client missing HTTP for stage instance")
1220
+
1221
+ await self._client._http.end_stage_instance(self.id, reason=reason)
1222
+
1223
+
1224
+ class StageInstance:
1225
+ """Represents a stage instance."""
1226
+
1227
+ def __init__(
1228
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1229
+ ) -> None:
1230
+ self._client = client_instance
1231
+ self.id: str = data["id"]
1232
+ self.guild_id: Optional[str] = data.get("guild_id")
1233
+ self.channel_id: str = data["channel_id"]
1234
+ self.topic: str = data["topic"]
1235
+ self.privacy_level: int = data.get("privacy_level", 2)
1236
+ self.discoverable_disabled: bool = data.get("discoverable_disabled", False)
1237
+ self.guild_scheduled_event_id: Optional[str] = data.get(
1238
+ "guild_scheduled_event_id"
1239
+ )
1240
+
1241
+ def __repr__(self) -> str:
1242
+ return f"<StageInstance id='{self.id}' channel_id='{self.channel_id}'>"
1243
+
1244
+
1165
1245
  class CategoryChannel(Channel):
1166
1246
  """Represents a guild category channel."""
1167
1247
 
@@ -1436,6 +1516,33 @@ class Webhook:
1436
1516
  return self._client.parse_message(message_data)
1437
1517
 
1438
1518
 
1519
+ class GuildTemplate:
1520
+ """Represents a guild template."""
1521
+
1522
+ def __init__(
1523
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
1524
+ ):
1525
+ self._client = client_instance
1526
+ self.code: str = data["code"]
1527
+ self.name: str = data["name"]
1528
+ self.description: Optional[str] = data.get("description")
1529
+ self.usage_count: int = data.get("usage_count", 0)
1530
+ self.creator_id: str = data.get("creator_id", "")
1531
+ self.creator: Optional[User] = (
1532
+ User(data["creator"]) if data.get("creator") else None
1533
+ )
1534
+ self.created_at: Optional[str] = data.get("created_at")
1535
+ self.updated_at: Optional[str] = data.get("updated_at")
1536
+ self.source_guild_id: Optional[str] = data.get("source_guild_id")
1537
+ self.serialized_source_guild: Dict[str, Any] = data.get(
1538
+ "serialized_source_guild", {}
1539
+ )
1540
+ self.is_dirty: Optional[bool] = data.get("is_dirty")
1541
+
1542
+ def __repr__(self) -> str:
1543
+ return f"<GuildTemplate code='{self.code}' name='{self.name}'>"
1544
+
1545
+
1439
1546
  # --- Message Components ---
1440
1547
 
1441
1548
 
@@ -1981,6 +2088,77 @@ class Reaction:
1981
2088
  return f"<Reaction message_id='{self.message_id}' user_id='{self.user_id}' emoji='{emoji_value}'>"
1982
2089
 
1983
2090
 
2091
+ class ScheduledEvent:
2092
+ """Represents a guild scheduled event."""
2093
+
2094
+ def __init__(
2095
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
2096
+ ):
2097
+ self._client = client_instance
2098
+ self.id: str = data["id"]
2099
+ self.guild_id: str = data["guild_id"]
2100
+ self.channel_id: Optional[str] = data.get("channel_id")
2101
+ self.creator_id: Optional[str] = data.get("creator_id")
2102
+ self.name: str = data["name"]
2103
+ self.description: Optional[str] = data.get("description")
2104
+ self.scheduled_start_time: str = data["scheduled_start_time"]
2105
+ self.scheduled_end_time: Optional[str] = data.get("scheduled_end_time")
2106
+ self.privacy_level: GuildScheduledEventPrivacyLevel = (
2107
+ GuildScheduledEventPrivacyLevel(data["privacy_level"])
2108
+ )
2109
+ self.status: GuildScheduledEventStatus = GuildScheduledEventStatus(
2110
+ data["status"]
2111
+ )
2112
+ self.entity_type: GuildScheduledEventEntityType = GuildScheduledEventEntityType(
2113
+ data["entity_type"]
2114
+ )
2115
+ self.entity_id: Optional[str] = data.get("entity_id")
2116
+ self.entity_metadata: Optional[Dict[str, Any]] = data.get("entity_metadata")
2117
+ self.creator: Optional[User] = (
2118
+ User(data["creator"]) if data.get("creator") else None
2119
+ )
2120
+ self.user_count: Optional[int] = data.get("user_count")
2121
+ self.image: Optional[str] = data.get("image")
2122
+
2123
+ def __repr__(self) -> str:
2124
+ return f"<ScheduledEvent id='{self.id}' name='{self.name}'>"
2125
+
2126
+
2127
+ @dataclass
2128
+ class Invite:
2129
+ """Represents a Discord invite."""
2130
+
2131
+ code: str
2132
+ channel_id: Optional[str]
2133
+ guild_id: Optional[str]
2134
+ inviter_id: Optional[str]
2135
+ uses: Optional[int]
2136
+ max_uses: Optional[int]
2137
+ max_age: Optional[int]
2138
+ temporary: Optional[bool]
2139
+ created_at: Optional[str]
2140
+
2141
+ @classmethod
2142
+ def from_dict(cls, data: Dict[str, Any]) -> "Invite":
2143
+ channel = data.get("channel")
2144
+ guild = data.get("guild")
2145
+ inviter = data.get("inviter")
2146
+ return cls(
2147
+ code=data["code"],
2148
+ channel_id=(channel or {}).get("id") if channel else data.get("channel_id"),
2149
+ guild_id=(guild or {}).get("id") if guild else data.get("guild_id"),
2150
+ inviter_id=(inviter or {}).get("id"),
2151
+ uses=data.get("uses"),
2152
+ max_uses=data.get("max_uses"),
2153
+ max_age=data.get("max_age"),
2154
+ temporary=data.get("temporary"),
2155
+ created_at=data.get("created_at"),
2156
+ )
2157
+
2158
+ def __repr__(self) -> str:
2159
+ return f"<Invite code='{self.code}' guild_id='{self.guild_id}' channel_id='{self.channel_id}'>"
2160
+
2161
+
1984
2162
  class GuildMemberRemove:
1985
2163
  """Represents a GUILD_MEMBER_REMOVE event."""
1986
2164
 
@@ -2039,6 +2217,25 @@ class GuildRoleUpdate:
2039
2217
  return f"<GuildRoleUpdate guild_id='{self.guild_id}' role_id='{self.role.id}'>"
2040
2218
 
2041
2219
 
2220
+ class AuditLogEntry:
2221
+ """Represents a single entry in a guild's audit log."""
2222
+
2223
+ def __init__(
2224
+ self, data: Dict[str, Any], client_instance: Optional["Client"] = None
2225
+ ) -> None:
2226
+ self._client = client_instance
2227
+ self.id: str = data["id"]
2228
+ self.user_id: Optional[str] = data.get("user_id")
2229
+ self.target_id: Optional[str] = data.get("target_id")
2230
+ self.action_type: int = data["action_type"]
2231
+ self.reason: Optional[str] = data.get("reason")
2232
+ self.changes: List[Dict[str, Any]] = data.get("changes", [])
2233
+ self.options: Optional[Dict[str, Any]] = data.get("options")
2234
+
2235
+ def __repr__(self) -> str:
2236
+ return f"<AuditLogEntry id='{self.id}' action_type={self.action_type} user_id='{self.user_id}'>"
2237
+
2238
+
2042
2239
  def channel_factory(data: Dict[str, Any], client: "Client") -> Channel:
2043
2240
  """Create a channel object from raw API data."""
2044
2241
  channel_type = data.get("type")
@@ -2048,11 +2245,10 @@ def channel_factory(data: Dict[str, Any], client: "Client") -> Channel:
2048
2245
  ChannelType.GUILD_ANNOUNCEMENT.value,
2049
2246
  ):
2050
2247
  return TextChannel(data, client)
2051
- if channel_type in (
2052
- ChannelType.GUILD_VOICE.value,
2053
- ChannelType.GUILD_STAGE_VOICE.value,
2054
- ):
2248
+ if channel_type == ChannelType.GUILD_VOICE.value:
2055
2249
  return VoiceChannel(data, client)
2250
+ if channel_type == ChannelType.GUILD_STAGE_VOICE.value:
2251
+ return StageChannel(data, client)
2056
2252
  if channel_type == ChannelType.GUILD_CATEGORY.value:
2057
2253
  return CategoryChannel(data, client)
2058
2254
  if channel_type in (
disagreement/py.typed ADDED
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: disagreement
3
- Version: 0.1.0rc2
3
+ Version: 0.2.0rc1
4
4
  Summary: A Python library for the Discord API.
5
5
  Author-email: Slipstream <me@slipstreamm.dev>
6
6
  License: BSD 3-Clause
@@ -119,6 +119,22 @@ setup_logging(logging.INFO)
119
119
  setup_logging(logging.DEBUG, file="bot.log")
120
120
  ```
121
121
 
122
+ ### HTTP Session Options
123
+
124
+ Pass additional keyword arguments to ``aiohttp.ClientSession`` using the
125
+ ``http_options`` parameter when constructing :class:`disagreement.Client`:
126
+
127
+ ```python
128
+ client = disagreement.Client(
129
+ token=token,
130
+ http_options={"proxy": "http://localhost:8080"},
131
+ )
132
+ ```
133
+
134
+ These options are forwarded to ``HTTPClient`` when it creates the underlying
135
+ ``aiohttp.ClientSession``. You can specify a custom ``connector`` or any other
136
+ session parameter supported by ``aiohttp``.
137
+
122
138
  ### Defining Subcommands with `AppCommandGroup`
123
139
 
124
140
  ```python
@@ -1,22 +1,23 @@
1
- disagreement/__init__.py,sha256=4T6_19N6SjwgFXFONo6GjB3VbgODPZUc5soxJoVS_zY,1084
1
+ disagreement/__init__.py,sha256=tZlZwEhqnBj3CEfPX2BWXXvGIc5OIiLtzuybRG7w1wA,1184
2
2
  disagreement/audio.py,sha256=P6inobI8CNhNVkaRKU58RMYtLq1RrSREioF0Mui5VlA,3351
3
3
  disagreement/cache.py,sha256=juabGFl4naQih5OUIVN2aN-vAfw2ZC2cI38s4nGEn8U,1525
4
- disagreement/client.py,sha256=7or564FS7fXALDyRvNrt-H32mtV1VLysuIs3CtQ9L3s,53182
5
- disagreement/color.py,sha256=g-1ynMGCUbY0f6jJXzMLS1aJFoZg91bdMetFkZgaCC0,2387
4
+ disagreement/client.py,sha256=WJ1xLiXMha0_9i0rUDxXiMWXSG80_fXylq3Qmf9rr7k,59855
5
+ disagreement/color.py,sha256=0RumZU9geS51rmmywwotmkXFc8RyQghOviRGGrHmvW4,4495
6
6
  disagreement/components.py,sha256=tEYJ2RHVpIFtZuPPxZ0v8ssUw_x7ybhYBzHNsRiXXvU,5250
7
- disagreement/enums.py,sha256=CP03oF28maaPUVckOfE_tnVIm2ZOc5WL8mWlocmjeOQ,9785
7
+ disagreement/enums.py,sha256=Km9rzmbkYdBpba3fDAv9YYtXDROoRpKuQfkMavsiY0s,11069
8
8
  disagreement/error_handler.py,sha256=c2lb6aTMnhTtITQuR6axZUtEaasYKUgmdSxAHEkeq50,1028
9
9
  disagreement/errors.py,sha256=XiYVPy8uFUGVi_EIf81yK7QbC7KyN4JHplSJSWw2RRk,3185
10
10
  disagreement/event_dispatcher.py,sha256=mp4LVhIj0SW1P2NruqbYpZoYH33X5rXvkAl3-RK40kE,11460
11
- disagreement/gateway.py,sha256=mhFtBm_YPtbleQJklv3ph3DXE4LZxC1BhtNkd7Y-akQ,23113
12
- disagreement/http.py,sha256=4FnVMIRjrNSpQ9IUEeWQGP6jYAhv3YTTANqFDNv0SdY,31046
11
+ disagreement/gateway.py,sha256=AxfGsSxu4eOWwpL3LQiNfcQVR3hyj33N9KfaPy0h8OU,24487
12
+ disagreement/http.py,sha256=TOGF2LBnsg4hTrP0sFBscKz1VVM_qZ8eoPZfBoQQPQw,37063
13
13
  disagreement/hybrid_context.py,sha256=VYCmcreTZdPBU9v-Cy48W38vgWO2t8nM2ulC6_z4HjU,1095
14
14
  disagreement/i18n.py,sha256=1L4rcFuKP0XjHk9dVwbNh4BkLk2ZlxxZ_-tecGWa9S0,718
15
15
  disagreement/interactions.py,sha256=aUZwwEOLsEds15i6G-rxmSSDCDmaxz_cfoTYS4tv6Ao,21735
16
16
  disagreement/logging_config.py,sha256=4q6baQPE6X_0lfaBTFMU1uqc03x5SbJqo2hsApdDFac,686
17
- disagreement/models.py,sha256=JeHKJWrc7mN70oFNgd7Iic9_SgHfDlgz7aTRgMA-5PA,75070
17
+ disagreement/models.py,sha256=Km75XDUiRV3gzhSPYDm2AByQEw2koZ-gyY1urvYffTE,82512
18
18
  disagreement/oauth.py,sha256=TfDdCwg1J7osM9wDi61dtNBA5BrQk5DeQrrHsYycH34,2810
19
19
  disagreement/permissions.py,sha256=7g5cIlg-evHXOL0-pmtT5EwqcB-stXot1HZSLz724sE,3008
20
+ disagreement/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
21
  disagreement/rate_limiter.py,sha256=ubwR_UTPs2MotipBdtqpgwQKx0IHt2I3cdfFcXTFv7g,2521
21
22
  disagreement/shard_manager.py,sha256=e9F8tx_4IEOlTX3-S3t51lfJToc6Ue3RVBzoNAiVKxs,2161
22
23
  disagreement/typing.py,sha256=_1oFWfZ4HyH5Q3bnF7CO24s79z-3_B5Qb69kWiwLhhU,1242
@@ -26,18 +27,18 @@ disagreement/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
26
27
  disagreement/ext/loader.py,sha256=9_uULvNAa-a6UiaeQhWglwgIrHEPKbf9bnWtSL1KV5Q,1408
27
28
  disagreement/ext/tasks.py,sha256=b14KI-btikbrjPlD76md3Ggt6znrxPqr7TDarU4PYBg,7269
28
29
  disagreement/ext/app_commands/__init__.py,sha256=mnQLIuGP9SzqGMPEn5YgOh2eIU7lcYoDXP06vtXZfTA,1014
29
- disagreement/ext/app_commands/commands.py,sha256=0O5fJQg2esTQzx2FyEpM2ZrrLckNmv8fs3TIPnr1Q_s,19020
30
+ disagreement/ext/app_commands/commands.py,sha256=xjrVPScEezdVxVq524_E2Gm2sTa-yq44TbGMu0gyA2o,19018
30
31
  disagreement/ext/app_commands/context.py,sha256=Xcm4Ka5K5uTQGviixF5LeCDdOdF9YQS5F7lZi2m--8s,20831
31
32
  disagreement/ext/app_commands/converters.py,sha256=J1VEmo-7H9K7kGPJodu5FX4RmFFI1BuzhlQAEs2MsD4,21036
32
33
  disagreement/ext/app_commands/decorators.py,sha256=dKiD4ZEsafRoPvfgn9zuQ9vvXXo2qYTMquHvyUM1604,23251
33
- disagreement/ext/app_commands/handler.py,sha256=XO9yLgcV7aIxzhTMgFcQ1Tbr4GRZRfDBzkIAkiu6mw8,26045
34
- disagreement/ext/app_commands/hybrid.py,sha256=yRDnlnOqgo79X669WhBHQ6LJCCuFDXKUbmlC_u3aXR0,3329
35
- disagreement/ext/commands/__init__.py,sha256=HxZWVfc4qvP_bCRbKTVZoMqXFq19Gj4mQvRumvQiApQ,1130
36
- disagreement/ext/commands/cog.py,sha256=U57yMrUpqj3_-W1-koyfGgH43MZG_JzJOl46kTur7iA,6636
34
+ disagreement/ext/app_commands/handler.py,sha256=KCMi5NEusuyLYo7Vk4sqLqXAJI5r3ppI0MNLUh0kU2c,28781
35
+ disagreement/ext/app_commands/hybrid.py,sha256=u3kHNbVfY3-liymgzEIkFO5YV3WM_DqwytzdN9EXWMY,3330
36
+ disagreement/ext/commands/__init__.py,sha256=miejXIfft2kq2Q4Lej28awSgQXIEEeEuaBhR3M7f9tk,1230
37
+ disagreement/ext/commands/cog.py,sha256=-F2ZOXXC07r96xlt9NomRgqlIqlcxzBiha2Zhg1DVp4,6845
37
38
  disagreement/ext/commands/converters.py,sha256=mh8xJr1FIiah6bdYy0KsdccfYcPii2Yc_IdhzCTw5uE,5864
38
- disagreement/ext/commands/core.py,sha256=vwsj3GR9wrEy0y-1HJv16Hg9-9xnm61tvT9b14QlYEI,19296
39
- disagreement/ext/commands/decorators.py,sha256=Ox_D9KCFtMa-RiljFjOcsPb3stmDStRKeLw1DVeOdAw,6608
40
- disagreement/ext/commands/errors.py,sha256=cG5sPA-osUq2gts5scrl5yT-BHEYVHLTb4TULjAmbaY,2065
39
+ disagreement/ext/commands/core.py,sha256=4AO-U9xFyDetWeQUZiqX_g4zZfue0-9s8QBnHIb2BTc,21265
40
+ disagreement/ext/commands/decorators.py,sha256=fOhppBae8gt-9QI1YqUzDctwOXmMBdAK_JaUJLNWHww,7427
41
+ disagreement/ext/commands/errors.py,sha256=L6losXKye62BqDl42fXxgkuAkG92W-OcqH9uwEgabb8,2301
41
42
  disagreement/ext/commands/help.py,sha256=yw0ydupOsPwmnhsIIoxa93xjj9MAcBcGfD8BXa7V8G8,1456
42
43
  disagreement/ext/commands/view.py,sha256=3Wo4gGJX9fb65qw8yHFwMjnAeJvMJAx19rZNHz-ZDUs,3315
43
44
  disagreement/ui/__init__.py,sha256=PLA6eHiq9cu7JDOKS-7MKtaFhlqswjbI4AEUlpnbgO0,307
@@ -46,8 +47,8 @@ disagreement/ui/item.py,sha256=bm-EmQEZpe8Kt8JrRw-o0uQdccgjErORcFsBqaXOcV8,1112
46
47
  disagreement/ui/modal.py,sha256=w0ZEVslXzx2-RWUP4jdVB54zDuT81jpueVWZ70byFnI,4137
47
48
  disagreement/ui/select.py,sha256=XjWRlWkA09QZaDDLn-wDDOWIuj0Mb4VCWJEOAaExZXw,3018
48
49
  disagreement/ui/view.py,sha256=QhWoYt39QKXwl1X6Mkm5gNNEqd8bt7O505lSpiG0L04,5567
49
- disagreement-0.1.0rc2.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
50
- disagreement-0.1.0rc2.dist-info/METADATA,sha256=qMql6NO40Fgv8w-yvoFgBq7V8glJbRJvN3-pyeDnQOY,4889
51
- disagreement-0.1.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
- disagreement-0.1.0rc2.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
53
- disagreement-0.1.0rc2.dist-info/RECORD,,
50
+ disagreement-0.2.0rc1.dist-info/licenses/LICENSE,sha256=zfmtgJhVFHnqT7a8LAQFthXu5bP7EEHmEL99trV66Ew,1474
51
+ disagreement-0.2.0rc1.dist-info/METADATA,sha256=ZEw0xKaAbZyXurATHmXCYwODfC6pr98jo27SlubhC_Q,5382
52
+ disagreement-0.2.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ disagreement-0.2.0rc1.dist-info/top_level.txt,sha256=t7FY_3iaYhdB6X6y9VybJ2j7UZbVeRUe9wRgH8d5Gtk,13
54
+ disagreement-0.2.0rc1.dist-info/RECORD,,