Skip to content

Message: Add unreact() method - #115

Merged
PenguinBoi12 merged 2 commits into
Code-Society-Lab:mainfrom
AnayGarodia:feat/message-unreact
Jul 23, 2026
Merged

Message: Add unreact() method#115
PenguinBoi12 merged 2 commits into
Code-Society-Lab:mainfrom
AnayGarodia:feat/message-unreact

Conversation

@AnayGarodia

Copy link
Copy Markdown
Contributor

Summary

  • add Message.unreact(emoji) to locate the client's matching reaction event and redact it
  • leave the call as a no-op when the client has no matching reaction
  • translate relation and redaction failures into MatrixError with the requested message
  • cover successful removal, another user's reaction, and Matrix API errors

Verification

  • black --fast --check matrix/ tests/
  • mypy matrix/
  • pytest tests/test_message.py -q (32 passed)
  • pytest -q (354 passed)

Closes #114

Signed-off-by: anay <agarodia98@gmail.com>
Copilot AI review requested due to automatic review settings July 22, 2026 21:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@PenguinBoi12 PenguinBoi12 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally it's pretty good, well done. There's only one maintainability issue and a missing test I'd like you to address before I approve this PR. Don't hesitate if you have any questions.

Comment thread matrix/message.py Outdated
"""
reaction_event_id = None
try:
async for event in self.client.room_get_event_relations(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of duplicating the logic here that we already have in fetch_reactions. However, we can't use fetch_reactions directly because it doesn't contain the information we need (event_id). There's a couple of solutions but I believe the simplest solution for now is extracting the iteration over reactions in a private helper method:

# types.py
@dataclass
class ReactionEvent:
    emoji: str
    sender: str
    event_id: str



# message.py (message class)
async def _iter_reaction_events(self) -> AsyncIterator[ReactionEvent]:
    """Yield (emoji, sender, event_id) for each reaction relation on this message."""
    try:
        async for event in self.client.room_get_event_relations(
            room_id=self.room.room_id,
            event_id=self.event_id,
        ):
            emoji = getattr(event, "key", None)
            sender = getattr(event, "sender", None)
            event_id = getattr(event, "event_id", None)

            if emoji and sender and event_id:
                yield ReactionEvent(emoji=emoji, sender=sender, event_id=event_id)
    except Exception as e:
        raise MatrixError(f"Failed to fetch reactions: {e}")

And then you can use it in both fetch_reactions and unreact:

# message.py (message class)
async def fetch_reactions(self) -> list[Reaction]:
    """..."""
    raw: dict[str, list[str]] = {}

    async for reaction_event in self._iter_reaction_events():
        raw.setdefault(reaction_event.emoji, []).append(reaction_event.sender)

    return [Reaction(key=emoji, senders=senders) for emoji, senders in raw.items()]

...

async def unreact(self, emoji: str) -> None:
    """..."""
    reaction_event_id = None

    async for reaction_event in self._iter_reaction_events():
        if reaction_event.emoji == emoji and reaction_event.sender == self.client.user_id:
            reaction_event_id = reaction_event.event_id
            break

    if reaction_event_id is None:
        return

    await matrix_call(
        self.client.room_redact(
            room_id=self.room.room_id,
            event_id=reaction_event_id,
        ),
        error_message="Failed to remove reaction",
    )

Ideally, we would bring changes to the existing Reaction dataclass so that instead of having sender we have list of ReactionEvent but it might be too out of scope of this PR so it's not required. The changes above are enough for now.

Comment thread matrix/message.py Outdated
if reaction_event_id:
break
except Exception as e:
raise MatrixError(f"Failed to remove reaction: {e}") from e

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this will go away with the changes from the comment above, the exception message is identical to the next matrix_call's message which could lead to confusing

Comment thread tests/test_message.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test coverage overall, but there's a small gap: unreact wraps the relation loop, but none of the three tests actually test the possible exception raising from there.

Something like:

@pytest.mark.asyncio
async def test_unreact_with_relations_error__expect_matrix_error(message, client):
    async def mock_relations(*args, **kwargs):
        raise Exception("Network error")
        yield

    client.room_get_event_relations = mock_relations

    with pytest.raises(MatrixError, match="Failed to fetch reactions: ..."):
        await message.unreact("👍")

This is similar to test_fetch_reactions_with_error__expect_matrix_error

Signed-off-by: anay <agarodia98@gmail.com>
@AnayGarodia

Copy link
Copy Markdown
Contributor Author

Addressed all requested changes in a2f9b50:

  • added a private async reaction-event iterator and reused it from both fetch_reactions() and unreact()
  • added the ReactionEvent dataclass with emoji, sender, and event ID
  • added coverage for relation-fetch failures in unreact()
  • fixed the EOF hook finding in examples/config.yaml

Full pre-commit run --all-files passes, including Black, mypy, and pytest. Ready for re-review.

Comment thread examples/config.yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like editor changed some whitespace or something, but it doesn't seem to change anything. Just be careful with that in the future.

@PenguinBoi12 PenguinBoi12 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🦭

Thanks for the contribution and don't forget to ⭐

@PenguinBoi12
PenguinBoi12 merged commit b844ac4 into Code-Society-Lab:main Jul 23, 2026
4 checks passed
PenguinBoi12 pushed a commit that referenced this pull request Jul 30, 2026
* feat(message): add unreact method

Signed-off-by: anay <agarodia98@gmail.com>

* refactor(message): share reaction iteration

Signed-off-by: anay <agarodia98@gmail.com>

---------

Signed-off-by: anay <agarodia98@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Message: Add unreact() method

3 participants