Skip to content

Commit c3fc966

Browse files
authored
Merge: v5.2.0
5.2.0
2 parents 758d80c + 4b8b735 commit c3fc966

File tree

23 files changed

+123
-63
lines changed

23 files changed

+123
-63
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ repos:
3030
- id: check-merge-conflict
3131
name: Merge Conflicts
3232
- repo: https://github.com/charliermarsh/ruff-pre-commit
33-
rev: 'v0.0.254'
33+
rev: 'v0.0.261'
3434
hooks:
3535
- id: ruff
3636
args: [--fix, --exit-non-zero-on-fix]
3737
- repo: https://github.com/psf/black
38-
rev: 23.1.0
38+
rev: 23.3.0
3939
hooks:
4040
- id: black
4141
name: Black Formatting

docs/src/Guides/22 Live Patching.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,12 @@ That's it! The extension will handle all of the leg work, and all you'll notice
1717
## How is this useful?
1818

1919
interactions.py takes advantage of jurigged to reload any and all commands that were edited whenever a change is made, allowing you to have more uptime with while still adding/improving features of your bot.
20+
21+
## It's not working inside Docker!
22+
To make `jurigged` work inside Docker container, you need to mount the directory you're working in as a volume in the container (pointing to the code directory inside the container).
23+
24+
Additionally, you need to initialize the `jurigged` extension with the `poll` keyword argument set to `True`:
25+
26+
```py
27+
bot.load_extension("interactions.ext.jurigged", poll=True)
28+
```

docs/src/Guides/25 Error Tracking.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ from interactions.api.events import Error
1515

1616
@listen()
1717
async def on_error(error: Error):
18-
await bot.get_channel(LOGGING_CHANNEL_ID).send(f"```\n{error.source}\n{error.error}\n```)
18+
await bot.get_channel(LOGGING_CHANNEL_ID).send(f"```\n{error.source}\n{error.error}\n```")
1919
```
2020

2121
And this is great when debugging. But it consumes your rate limit, can run into the 2000 character message limit, and won't work on shards that don't contain your personal server. It's also very hard to notice patterns and can be noisy.

interactions/api/gateway/websocket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ async def send_json(self, data: dict, bypass=False) -> None:
164164
serialized = FastJson.dumps(data)
165165
await self.send(serialized, bypass)
166166

167-
async def receive(self, force: bool = False) -> str:
167+
async def receive(self, force: bool = False) -> str: # noqa: C901
168168
"""
169169
Receive a full event payload from the WebSocket.
170170

interactions/api/http/http_client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def _process_payload(
334334
form_data.add_field("payload_json", FastJson.dumps(payload))
335335
return form_data
336336

337-
async def request(
337+
async def request( # noqa: C901
338338
self,
339339
route: Route,
340340
payload: list | dict | None = None,
@@ -403,7 +403,7 @@ async def request(
403403
# resource ratelimit is reached
404404
self.log_ratelimit(
405405
self.logger.warning,
406-
f"{route.endpoint} The resource is being rate limited! "
406+
f"{route.resolved_endpoint} The resource is being rate limited! "
407407
f"Reset in {result.get('retry_after')} seconds",
408408
)
409409
# lock this resource and wait for unlock
@@ -414,15 +414,15 @@ async def request(
414414
# so long as these are infrequent we're doing well
415415
self.log_ratelimit(
416416
self.logger.warning,
417-
f"{route.endpoint} Has exceeded it's ratelimit ({lock.limit})! Reset in {lock.delta} seconds",
417+
f"{route.resolved_endpoint} Has exceeded its ratelimit ({lock.limit})! Reset in {lock.delta} seconds",
418418
)
419419
await lock.lock_for_duration(lock.delta, block=True)
420420
continue
421421
if lock.remaining == 0:
422422
# Last call available in the bucket, lock until reset
423423
self.log_ratelimit(
424424
self.logger.debug,
425-
f"{route.endpoint} Has exhausted its ratelimit ({lock.limit})! Locking route for {lock.delta} seconds",
425+
f"{route.resolved_endpoint} Has exhausted its ratelimit ({lock.limit})! Locking route for {lock.delta} seconds",
426426
)
427427
await lock.lock_for_duration(
428428
lock.delta
@@ -431,7 +431,7 @@ async def request(
431431
elif response.status in {500, 502, 504}:
432432
# Server issues, retry
433433
self.logger.warning(
434-
f"{route.endpoint} Received {response.status}... retrying in {1 + attempt * 2} seconds"
434+
f"{route.resolved_endpoint} Received {response.status}... retrying in {1 + attempt * 2} seconds"
435435
)
436436
await asyncio.sleep(1 + attempt * 2)
437437
continue
@@ -440,7 +440,7 @@ async def request(
440440
await self._raise_exception(response, route, result)
441441

442442
self.logger.debug(
443-
f"{route.endpoint} Received {response.status} :: [{lock.remaining}/{lock.limit} calls remaining]"
443+
f"{route.resolved_endpoint} Received {response.status} :: [{lock.remaining}/{lock.limit} calls remaining]"
444444
)
445445
return result
446446
except OSError as e:

interactions/api/http/http_requests/channels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ async def get_invite(
376376
}
377377
params = dict_filter_none(params)
378378

379-
result = await self.request(Route("GET", "/invites/{invite_code}", params=params))
379+
result = await self.request(Route("GET", "/invites/{invite_code}", invite_code=invite_code, params=params))
380380
return cast(discord_typings.InviteData, result)
381381

382382
async def delete_invite(self, invite_code: str, reason: str | None = None) -> discord_typings.InviteData:

interactions/api/http/http_requests/threads.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ async def create_thread(
180180
auto_archive_duration: int,
181181
thread_type: Absent[int] = MISSING,
182182
invitable: Absent[bool] = MISSING,
183+
rate_limit_per_user: Absent[int] = MISSING,
183184
message_id: Absent["Snowflake_Type"] = MISSING,
184185
reason: Absent[str] = MISSING,
185186
) -> discord_typings.ThreadChannelData:
@@ -191,15 +192,20 @@ async def create_thread(
191192
name: The name of the thread
192193
auto_archive_duration: duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080
193194
thread_type: The type of thread, defaults to public. ignored if creating thread from a message
194-
invitable:
195+
invitable: Whether non-moderators can add other non-moderators to a thread; only available when creating a private thread
196+
rate_limit_per_user: The time users must wait between sending messages (0-21600)
195197
message_id: An optional message to create a thread from.
196198
reason: An optional reason for the audit log
197199
198200
Returns:
199201
The created thread
200202
201203
"""
202-
payload = {"name": name, "auto_archive_duration": auto_archive_duration}
204+
payload = {
205+
"name": name,
206+
"auto_archive_duration": auto_archive_duration,
207+
"rate_limit_per_user": rate_limit_per_user,
208+
}
203209
if message_id:
204210
return await self.request(
205211
Route(

interactions/api/http/route.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,36 @@ def rl_bucket(self) -> str:
5555
return f"{self.webhook_id}{self.webhook_token}:{self.channel_id}:{self.guild_id}:{self.endpoint}"
5656
return f"{self.channel_id}:{self.guild_id}:{self.endpoint}"
5757

58+
@property
59+
def major_params(self) -> dict[str, str | int]:
60+
"""The major parameters for this route"""
61+
return {
62+
"channel_id": self.channel_id,
63+
"guild_id": self.guild_id,
64+
"webhook_id": self.webhook_id,
65+
"webhook_token": self.webhook_token,
66+
}
67+
68+
@property
69+
def resolved_path(self) -> str:
70+
"""The endpoint for this route, with all parameters resolved"""
71+
return self.path.format_map({k: _uriquote(v) if isinstance(v, str) else v for k, v in self.params.items()})
72+
5873
@property
5974
def endpoint(self) -> str:
6075
"""The endpoint for this route"""
6176
return f"{self.method} {self.path}"
6277

78+
@property
79+
def resolved_endpoint(self) -> str:
80+
"""The endpoint for this route, with all major parameters resolved"""
81+
path = self.path
82+
for key, value in self.major_params.items():
83+
path = path.replace(f"{{{key}}}", str(value))
84+
85+
return f"{self.method} {path}"
86+
6387
@property
6488
def url(self) -> str:
6589
"""The full url for this route"""
66-
return f"{self.BASE}{self.path}".format_map(
67-
{k: _uriquote(v) if isinstance(v, str) else v for k, v in self.params.items()}
68-
)
90+
return f"{self.BASE}{self.resolved_path}"

interactions/api/voice/voice_gateway.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ async def run(self) -> None:
100100
# possible race conditions to consider.
101101
await self.dispatch_opcode(data, op)
102102

103-
async def receive(self, force=False) -> str:
103+
async def receive(self, force=False) -> str: # noqa: C901
104104
buffer = bytearray()
105105

106106
while True:

interactions/client/client.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,12 +1598,14 @@ def get_application_cmd_by_id(
15981598
The command, if one with the given ID exists internally, otherwise None
15991599
16001600
"""
1601+
cmd_id = to_snowflake(cmd_id)
1602+
scope = to_snowflake(scope) if scope is not None else None
1603+
16011604
if scope is not None:
1602-
return self.interactions_by_scope.get(scope, {}).get(cmd_id)
1603-
return next(
1604-
(scope[cmd_id] for scope in self.interactions_by_scope.values() if cmd_id in scope),
1605-
None,
1606-
)
1605+
return next(
1606+
(cmd for cmd in self.interactions_by_scope[scope].values() if cmd.get_cmd_id(scope) == cmd_id), None
1607+
)
1608+
return next(cmd for cmd in self._interaction_lookup.values() if cmd_id in cmd.cmd_id.values())
16071609

16081610
def _raise_sync_exception(self, e: HTTPException, cmds_json: dict, cmd_scope: "Snowflake_Type") -> NoReturn:
16091611
try:
@@ -1691,7 +1693,7 @@ async def _run_slash_command(self, command: SlashCommand, ctx: "InteractionConte
16911693
return await command(ctx, **ctx.kwargs)
16921694

16931695
@processors.Processor.define("raw_interaction_create")
1694-
async def _dispatch_interaction(self, event: RawGatewayEvent) -> None:
1696+
async def _dispatch_interaction(self, event: RawGatewayEvent) -> None: # noqa: C901
16951697
"""
16961698
Identify and dispatch interaction of slash commands or components.
16971699
@@ -1701,6 +1703,10 @@ async def _dispatch_interaction(self, event: RawGatewayEvent) -> None:
17011703
"""
17021704
interaction_data = event.data
17031705

1706+
if not self._startup:
1707+
self.logger.warning("Received interaction before startup completed, ignoring")
1708+
return
1709+
17041710
if interaction_data["type"] in (
17051711
InteractionType.APPLICATION_COMMAND,
17061712
InteractionType.AUTOCOMPLETE,

0 commit comments

Comments
 (0)