Add reaction event parsing and dispatch (#8)
This commit is contained in:
parent
2d127c26a2
commit
ad2db52af7
@ -17,7 +17,7 @@ __copyright__ = "Copyright 2025 Slipstream"
|
|||||||
__version__ = "0.0.2"
|
__version__ = "0.0.2"
|
||||||
|
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .models import Message, User
|
from .models import Message, User, Reaction
|
||||||
from .voice_client import VoiceClient
|
from .voice_client import VoiceClient
|
||||||
from .typing import Typing
|
from .typing import Typing
|
||||||
from .errors import (
|
from .errors import (
|
||||||
|
@ -922,6 +922,16 @@ class Client:
|
|||||||
|
|
||||||
await self._http.create_reaction(channel_id, message_id, emoji)
|
await self._http.create_reaction(channel_id, message_id, emoji)
|
||||||
|
|
||||||
|
user_id = getattr(getattr(self, "user", None), "id", None)
|
||||||
|
payload = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"message_id": message_id,
|
||||||
|
"emoji": {"name": emoji, "id": None},
|
||||||
|
}
|
||||||
|
if hasattr(self, "_event_dispatcher"):
|
||||||
|
await self._event_dispatcher.dispatch("MESSAGE_REACTION_ADD", payload)
|
||||||
|
|
||||||
async def delete_reaction(
|
async def delete_reaction(
|
||||||
self, channel_id: str, message_id: str, emoji: str
|
self, channel_id: str, message_id: str, emoji: str
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -932,6 +942,16 @@ class Client:
|
|||||||
|
|
||||||
await self._http.delete_reaction(channel_id, message_id, emoji)
|
await self._http.delete_reaction(channel_id, message_id, emoji)
|
||||||
|
|
||||||
|
user_id = getattr(getattr(self, "user", None), "id", None)
|
||||||
|
payload = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"message_id": message_id,
|
||||||
|
"emoji": {"name": emoji, "id": None},
|
||||||
|
}
|
||||||
|
if hasattr(self, "_event_dispatcher"):
|
||||||
|
await self._event_dispatcher.dispatch("MESSAGE_REACTION_REMOVE", payload)
|
||||||
|
|
||||||
async def get_reactions(
|
async def get_reactions(
|
||||||
self, channel_id: str, message_id: str, emoji: str
|
self, channel_id: str, message_id: str, emoji: str
|
||||||
) -> List["User"]:
|
) -> List["User"]:
|
||||||
|
@ -52,6 +52,8 @@ class EventDispatcher:
|
|||||||
"CHANNEL_CREATE": self._parse_channel_create,
|
"CHANNEL_CREATE": self._parse_channel_create,
|
||||||
"PRESENCE_UPDATE": self._parse_presence_update,
|
"PRESENCE_UPDATE": self._parse_presence_update,
|
||||||
"TYPING_START": self._parse_typing_start,
|
"TYPING_START": self._parse_typing_start,
|
||||||
|
"MESSAGE_REACTION_ADD": self._parse_message_reaction,
|
||||||
|
"MESSAGE_REACTION_REMOVE": self._parse_message_reaction,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _parse_message_create(self, data: Dict[str, Any]) -> Message:
|
def _parse_message_create(self, data: Dict[str, Any]) -> Message:
|
||||||
@ -88,6 +90,13 @@ class EventDispatcher:
|
|||||||
|
|
||||||
return TypingStart(data, client_instance=self._client)
|
return TypingStart(data, client_instance=self._client)
|
||||||
|
|
||||||
|
def _parse_message_reaction(self, data: Dict[str, Any]):
|
||||||
|
"""Parses raw reaction data into a Reaction object."""
|
||||||
|
|
||||||
|
from .models import Reaction
|
||||||
|
|
||||||
|
return Reaction(data, client_instance=self._client)
|
||||||
|
|
||||||
# Potentially add _parse_user for events that directly provide a full user object
|
# Potentially add _parse_user for events that directly provide a full user object
|
||||||
# def _parse_user_update(self, data: Dict[str, Any]) -> User:
|
# def _parse_user_update(self, data: Dict[str, Any]) -> User:
|
||||||
# return User(data=data)
|
# return User(data=data)
|
||||||
|
@ -1614,6 +1614,27 @@ class TypingStart:
|
|||||||
return f"<TypingStart channel_id='{self.channel_id}' user_id='{self.user_id}'>"
|
return f"<TypingStart channel_id='{self.channel_id}' user_id='{self.user_id}'>"
|
||||||
|
|
||||||
|
|
||||||
|
class Reaction:
|
||||||
|
"""Represents a message reaction event."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, data: Dict[str, Any], client_instance: Optional["Client"] = None
|
||||||
|
):
|
||||||
|
self._client = client_instance
|
||||||
|
self.user_id: str = data["user_id"]
|
||||||
|
self.channel_id: str = data["channel_id"]
|
||||||
|
self.message_id: str = data["message_id"]
|
||||||
|
self.guild_id: Optional[str] = data.get("guild_id")
|
||||||
|
self.member: Optional[Member] = (
|
||||||
|
Member(data["member"], client_instance) if data.get("member") else None
|
||||||
|
)
|
||||||
|
self.emoji: Dict[str, Any] = data.get("emoji", {})
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
emoji_value = self.emoji.get("name") or self.emoji.get("id")
|
||||||
|
return f"<Reaction message_id='{self.message_id}' user_id='{self.user_id}' emoji='{emoji_value}'>"
|
||||||
|
|
||||||
|
|
||||||
def channel_factory(data: Dict[str, Any], client: "Client") -> Channel:
|
def channel_factory(data: Dict[str, Any], client: "Client") -> Channel:
|
||||||
"""Create a channel object from raw API data."""
|
"""Create a channel object from raw API data."""
|
||||||
channel_type = data.get("type")
|
channel_type = data.get("type")
|
||||||
|
@ -4,7 +4,7 @@ from unittest.mock import AsyncMock
|
|||||||
|
|
||||||
from disagreement.client import Client
|
from disagreement.client import Client
|
||||||
from disagreement.errors import DisagreementException
|
from disagreement.errors import DisagreementException
|
||||||
from disagreement.models import User
|
from disagreement.models import User, Reaction
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -55,3 +55,37 @@ async def test_get_reactions_parses_users():
|
|||||||
|
|
||||||
http.get_reactions.assert_called_once_with("1", "2", "😀")
|
http.get_reactions.assert_called_once_with("1", "2", "😀")
|
||||||
assert isinstance(users[0], User)
|
assert isinstance(users[0], User)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_reaction_dispatches_event(monkeypatch):
|
||||||
|
http = SimpleNamespace(create_reaction=AsyncMock())
|
||||||
|
client = Client(token="t")
|
||||||
|
client._http = http
|
||||||
|
events = {}
|
||||||
|
|
||||||
|
async def on_add(reaction):
|
||||||
|
events["add"] = reaction
|
||||||
|
|
||||||
|
client._event_dispatcher.register("MESSAGE_REACTION_ADD", on_add)
|
||||||
|
|
||||||
|
await client.create_reaction("1", "2", "😀")
|
||||||
|
|
||||||
|
assert isinstance(events.get("add"), Reaction)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_reaction_dispatches_event(monkeypatch):
|
||||||
|
http = SimpleNamespace(delete_reaction=AsyncMock())
|
||||||
|
client = Client(token="t")
|
||||||
|
client._http = http
|
||||||
|
events = {}
|
||||||
|
|
||||||
|
async def on_remove(reaction):
|
||||||
|
events["remove"] = reaction
|
||||||
|
|
||||||
|
client._event_dispatcher.register("MESSAGE_REACTION_REMOVE", on_remove)
|
||||||
|
|
||||||
|
await client.delete_reaction("1", "2", "😀")
|
||||||
|
|
||||||
|
assert isinstance(events.get("remove"), Reaction)
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from disagreement.event_dispatcher import EventDispatcher
|
from disagreement.event_dispatcher import EventDispatcher
|
||||||
|
from disagreement.models import Reaction
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_reaction_payload():
|
async def test_reaction_payload():
|
||||||
# This test now checks the raw payload dictionary, as the Reaction model is removed.
|
|
||||||
data = {
|
data = {
|
||||||
"user_id": "1",
|
"user_id": "1",
|
||||||
"channel_id": "2",
|
"channel_id": "2",
|
||||||
"message_id": "3",
|
"message_id": "3",
|
||||||
"emoji": {"name": "😀", "id": None},
|
"emoji": {"name": "😀", "id": None},
|
||||||
}
|
}
|
||||||
# The "reaction" is just the data dictionary itself.
|
reaction = Reaction(data)
|
||||||
assert data["user_id"] == "1"
|
assert reaction.user_id == "1"
|
||||||
assert data["emoji"]["name"] == "😀"
|
assert reaction.emoji["name"] == "😀"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -21,7 +21,7 @@ async def test_dispatch_reaction_event(dummy_client):
|
|||||||
dispatcher = EventDispatcher(dummy_client)
|
dispatcher = EventDispatcher(dummy_client)
|
||||||
captured = []
|
captured = []
|
||||||
|
|
||||||
async def listener(payload: dict):
|
async def listener(payload: Reaction):
|
||||||
captured.append(payload)
|
captured.append(payload)
|
||||||
|
|
||||||
# The event name is now MESSAGE_REACTION_ADD as per the original test setup.
|
# The event name is now MESSAGE_REACTION_ADD as per the original test setup.
|
||||||
@ -35,4 +35,4 @@ async def test_dispatch_reaction_event(dummy_client):
|
|||||||
}
|
}
|
||||||
await dispatcher.dispatch("MESSAGE_REACTION_ADD", payload)
|
await dispatcher.dispatch("MESSAGE_REACTION_ADD", payload)
|
||||||
assert len(captured) == 1
|
assert len(captured) == 1
|
||||||
assert isinstance(captured[0], dict)
|
assert isinstance(captured[0], Reaction)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user