Skip to content

Commit 8963704

Browse files
authored
Merge: pull request #1381 from interactions-py/unstable
5.3.0
2 parents c3fc966 + da8a6dc commit 8963704

File tree

18 files changed

+147
-19
lines changed

18 files changed

+147
-19
lines changed

.github/workflows/pytest-push.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
4343
RUN_TESTBOT: ${{ matrix.RUN_TESTBOT }}
4444
run: |
45-
pytest
45+
pytest --cov=./ --cov-report xml:coverage.xml
4646
coverage xml -i
4747
- name: Upload Coverage
4848
run: |

interactions/api/events/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class BaseEvent:
2727
bot: "Client" = attrs.field(repr=False, kw_only=True, default=MISSING)
2828
"""The client instance that dispatched this event."""
2929

30+
@property
31+
def client(self) -> "Client":
32+
"""The client instance that dispatched this event."""
33+
return self.bot
34+
3035
@property
3136
def resolved_name(self) -> str:
3237
"""The name of the event, defaults to the class name if not overridden."""

interactions/api/events/processors/reaction_events.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import interactions.api.events as events
44
from interactions.models import PartialEmoji, Reaction
5+
56
from ._template import EventMixinTemplate, Processor
67

78
if TYPE_CHECKING:
@@ -14,6 +15,8 @@ class ReactionEvents(EventMixinTemplate):
1415
async def _handle_message_reaction_change(self, event: "RawGatewayEvent", add: bool) -> None:
1516
if member := event.data.get("member"):
1617
author = self.cache.place_member_data(event.data.get("guild_id"), member)
18+
elif guild_id := event.data.get("guild_id"):
19+
author = await self.cache.fetch_member(guild_id, event.data.get("user_id"))
1720
else:
1821
author = await self.cache.fetch_user(event.data.get("user_id"))
1922

interactions/client/client.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,11 +571,17 @@ async def _async_wrap(_coro: Listener, _event: BaseEvent, *_args, **_kwargs) ->
571571
):
572572
await self.wait_until_ready()
573573

574-
if len(_event.__attrs_attrs__) == 2 and coro.event != "event":
575-
# override_name & bot & logging
576-
await _coro()
577-
else:
574+
# don't pass event object if listener doesn't expect it
575+
if _coro.pass_event_object:
578576
await _coro(_event, *_args, **_kwargs)
577+
else:
578+
if not _coro.warned_no_event_arg and len(_event.__attrs_attrs__) > 2 and _coro.event != "event":
579+
self.logger.warning(
580+
f"{_coro} is listening to {_coro.event} event which contains event data. "
581+
f"Add an event argument to this listener to receive the event data object."
582+
)
583+
_coro.warned_no_event_arg = True
584+
await _coro()
579585
except asyncio.CancelledError:
580586
pass
581587
except Exception as e:
@@ -1202,6 +1208,8 @@ def add_listener(self, listener: Listener) -> None:
12021208
self.logger.debug(f"Listener {listener} has already been hooked, not re-hooking it again")
12031209
return
12041210

1211+
listener.lazy_parse_params()
1212+
12051213
if listener.event not in self.listeners:
12061214
self.listeners[listener.event] = []
12071215
self.listeners[listener.event].append(listener)

interactions/ext/prefixed_commands/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ async def _register_command(self, event: CallbackAdded) -> None:
239239
@listen("extension_unload")
240240
async def _handle_ext_unload(self, event: ExtensionUnload) -> None:
241241
"""Unregisters all prefixed commands in an extension as it is being unloaded."""
242-
for name in self._ext_command_list[event.extension.extension_name]:
242+
for name in self._ext_command_list[event.extension.extension_name].copy():
243243
self.remove_command(name)
244244

245245
@listen("raw_message_create", is_default_listener=True)

interactions/models/discord/auto_mod.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ class HarmfulLinkFilter(BaseTrigger):
121121
repr=True,
122122
metadata=docs("The type of trigger"),
123123
)
124-
...
125124

126125

127126
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
@@ -151,12 +150,24 @@ class MentionSpamTrigger(BaseTrigger):
151150
)
152151

153152

153+
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
154+
class MemberProfileTrigger(BaseTrigger):
155+
regex_patterns: list[str] = attrs.field(
156+
factory=list, repr=True, metadata=docs("The regex patterns to check against")
157+
)
158+
keyword_filter: str | list[str] = attrs.field(
159+
factory=list, repr=True, metadata=docs("The keywords to check against")
160+
)
161+
allow_list: list["Snowflake_Type"] = attrs.field(
162+
factory=list, repr=True, metadata=docs("The roles exempt from this rule")
163+
)
164+
165+
154166
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
155167
class BlockMessage(BaseAction):
156168
"""blocks the content of a message according to the rule"""
157169

158170
type: AutoModAction = attrs.field(repr=False, default=AutoModAction.BLOCK_MESSAGE, converter=AutoModAction)
159-
...
160171

161172

162173
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
@@ -175,6 +186,13 @@ class TimeoutUser(BaseAction):
175186
type: AutoModAction = attrs.field(repr=False, default=AutoModAction.TIMEOUT_USER, converter=AutoModAction)
176187

177188

189+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
190+
class BlockMemberInteraction(BaseAction):
191+
"""Block a member from using text, voice, or other interactions"""
192+
193+
# this action has no metadata
194+
195+
178196
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
179197
class AutoModRule(DiscordObject):
180198
"""A representation of an auto mod rule"""
@@ -345,11 +363,13 @@ def member(self) -> "Optional[Member]":
345363
AutoModAction.BLOCK_MESSAGE: BlockMessage,
346364
AutoModAction.ALERT_MESSAGE: AlertMessage,
347365
AutoModAction.TIMEOUT_USER: TimeoutUser,
366+
AutoModAction.BLOCK_MEMBER_INTERACTION: BlockMemberInteraction,
348367
}
349368

350369
TRIGGER_MAPPING = {
351370
AutoModTriggerType.KEYWORD: KeywordTrigger,
352371
AutoModTriggerType.HARMFUL_LINK: HarmfulLinkFilter,
353372
AutoModTriggerType.KEYWORD_PRESET: KeywordPresetTrigger,
354373
AutoModTriggerType.MENTION_SPAM: MentionSpamTrigger,
374+
AutoModTriggerType.MEMBER_PROFILE: MemberProfileTrigger,
355375
}

interactions/models/discord/channel.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,6 +1901,11 @@ def permission_overwrites(self) -> List["PermissionOverwrite"]:
19011901
"""The permission overwrites for this channel."""
19021902
return []
19031903

1904+
@property
1905+
def clyde_created(self) -> bool:
1906+
"""Whether this thread was created by Clyde."""
1907+
return ChannelFlags.CLYDE_THREAD in self.flags
1908+
19041909
def permissions_for(self, instance: Snowflake_Type) -> Permissions:
19051910
"""
19061911
Calculates permissions for an instance

interactions/models/discord/enums.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ class MessageFlags(DiscordIntFlag): # type: ignore
457457
"""This message contains a abusive website link, pops up a warning when clicked"""
458458
SILENT = 1 << 12
459459
"""This message should not trigger push or desktop notifications"""
460+
VOICE_MESSAGE = 1 << 13
461+
"""This message is a voice message"""
460462

461463
# Special members
462464
NONE = 0
@@ -551,6 +553,16 @@ class Permissions(DiscordIntFlag): # type: ignore
551553
"""Allows for using Activities (applications with the `EMBEDDED` flag) in a voice channel"""
552554
MODERATE_MEMBERS = 1 << 40
553555
"""Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels"""
556+
VIEW_CREATOR_MONETIZATION_ANALYTICS = 1 << 41
557+
"""Allows for viewing guild monetization insights"""
558+
USE_SOUNDBOARD = 1 << 42
559+
"""Allows for using the soundboard in a voice channel"""
560+
CREATE_GUILD_EXPRESSIONS = 1 << 43
561+
"""Allows for creating emojis, stickers, and soundboard sounds"""
562+
USE_EXTERNAL_SOUNDS = 1 << 45
563+
"""Allows the usage of custom sounds from other servers"""
564+
SEND_VOICE_MESSAGES = 1 << 46
565+
"""Allows for sending audio messages"""
554566

555567
# Shortcuts/grouping/aliases
556568
REQUIRES_MFA = (
@@ -780,6 +792,8 @@ class SystemChannelFlags(DiscordIntFlag):
780792
class ChannelFlags(DiscordIntFlag):
781793
PINNED = 1 << 1
782794
""" Thread is pinned to the top of its parent forum channel """
795+
CLYDE_THREAD = 1 << 8
796+
"""This thread was created by Clyde"""
783797

784798
# Special members
785799
NONE = 0
@@ -964,6 +978,7 @@ class AuditLogEventType(CursedIntEnum):
964978
ONBOARDING_UPDATE = 167
965979
GUILD_HOME_FEATURE_ITEM = 171
966980
GUILD_HOME_FEATURE_ITEM_UPDATE = 172
981+
BLOCKED_PHISHING_LINK = 180
967982
SERVER_GUIDE_CREATE = 190
968983
SERVER_GUIDE_UPDATE = 191
969984

@@ -974,16 +989,19 @@ class AutoModTriggerType(CursedIntEnum):
974989
SPAM = 3
975990
KEYWORD_PRESET = 4
976991
MENTION_SPAM = 5
992+
MEMBER_PROFILE = 6
977993

978994

979995
class AutoModAction(CursedIntEnum):
980996
BLOCK_MESSAGE = 1
981997
ALERT_MESSAGE = 2
982998
TIMEOUT_USER = 3
999+
BLOCK_MEMBER_INTERACTION = 4
9831000

9841001

9851002
class AutoModEvent(CursedIntEnum):
9861003
MESSAGE_SEND = 1
1004+
MEMBER_UPDATE = 2
9871005

9881006

9891007
class AutoModLanuguageType(Enum):

interactions/models/discord/guild.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,21 @@ def voice_states(self) -> List["models.VoiceState"]:
439439
# noinspection PyProtectedMember
440440
return [v_state for v_state in self._client.cache.voice_state_cache.values() if v_state._guild_id == self.id]
441441

442+
@property
443+
def mention_onboarding_customize(self) -> str:
444+
"""Return a mention string for the customise section of Onboarding"""
445+
return "<id:customize>"
446+
447+
@property
448+
def mention_onboarding_browse(self) -> str:
449+
"""Return a mention string for the browse section of Onboarding"""
450+
return "<id:browse>"
451+
452+
@property
453+
def mention_onboarding_guide(self) -> str:
454+
"""Return a mention string for the guide section of Onboarding"""
455+
return "<id:guide>"
456+
442457
async def fetch_member(self, member_id: Snowflake_Type, *, force: bool = False) -> Optional["models.Member"]:
443458
"""
444459
Return the Member with the given discord ID, fetching from the API if necessary.

interactions/models/discord/message.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import base64
23
import re
34
from dataclasses import dataclass
45
from typing import (
@@ -89,12 +90,22 @@ class Attachment(DiscordObject):
8990
"""width of file (if image)"""
9091
ephemeral: bool = attrs.field(repr=False, default=False)
9192
"""whether this attachment is ephemeral"""
93+
duration_secs: Optional[int] = attrs.field(repr=False, default=None)
94+
"""the duration of the audio file (currently for voice messages)"""
95+
waveform: bytearray = attrs.field(repr=False, default=None)
96+
"""base64 encoded bytearray representing a sampled waveform (currently for voice messages)"""
9297

9398
@property
9499
def resolution(self) -> tuple[Optional[int], Optional[int]]:
95100
"""Returns the image resolution of the attachment file"""
96101
return self.height, self.width
97102

103+
@classmethod
104+
def _process_dict(cls, data: Dict[str, Any], _) -> Dict[str, Any]:
105+
if waveform := data.pop("waveform", None):
106+
data["waveform"] = bytearray(base64.b64decode(waveform))
107+
return data
108+
98109

99110
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
100111
class ChannelMention(DiscordObject):
@@ -369,6 +380,13 @@ def thread(self) -> "models.TYPE_THREAD_CHANNEL":
369380
"""The thread that was started from this message, if any"""
370381
return self._client.cache.get_channel(self.id)
371382

383+
@property
384+
def editable(self) -> bool:
385+
"""Whether this message can be edited by the current user"""
386+
if self.author.id == self._client.user.id:
387+
return MessageFlags.VOICE_MESSAGE not in self.flags
388+
return False
389+
372390
async def fetch_referenced_message(self, *, force: bool = False) -> Optional["Message"]:
373391
"""
374392
Fetch the message this message is referencing, if any.

0 commit comments

Comments
 (0)