matrix-alertbot/my_project_name/callbacks.py
Andrew Morgan 647233cfac Add example commands for reactions and event reply
There is now a 'react' command that the bot will react to when used. When a reaction is
made on a message that the bot sent, then it will acknowledge that reaction using a
reply.
2021-01-03 23:53:45 -05:00

152 lines
5.3 KiB
Python

import logging
from nio import JoinError, MatrixRoom, RoomGetEventError, UnknownEvent
from my_project_name.bot_commands import Command
from my_project_name.chat_functions import make_pill, send_text_to_room
from my_project_name.message_responses import Message
logger = logging.getLogger(__name__)
class Callbacks(object):
def __init__(self, client, store, config):
"""
Args:
client (nio.AsyncClient): nio client used to interact with matrix
store (Storage): Bot storage
config (Config): Bot configuration parameters
"""
self.client = client
self.store = store
self.config = config
self.command_prefix = config.command_prefix
async def message(self, room, event):
"""Callback for when a message event is received
Args:
room (nio.rooms.MatrixRoom): The room the event came from
event (nio.events.room_events.RoomMessageText): The event defining the message
"""
# Extract the message text
msg = event.body
# Ignore messages from ourselves
if event.sender == self.client.user:
return
logger.debug(
f"Bot message received for room {room.display_name} | "
f"{room.user_name(event.sender)}: {msg}"
)
# Process as message if in a public room without command prefix
has_command_prefix = msg.startswith(self.command_prefix)
# room.is_group is often a DM, but not always.
# room.is_group does not allow room aliases
# room.member_count > 2 ... we assume a public room
# room.member_count <= 2 ... we assume a DM
if not has_command_prefix and room.member_count > 2:
# General message listener
message = Message(self.client, self.store, self.config, msg, room, event)
await message.process()
return
# Otherwise if this is in a 1-1 with the bot or features a command prefix,
# treat it as a command
if has_command_prefix:
# Remove the command prefix
msg = msg[len(self.command_prefix) :]
command = Command(self.client, self.store, self.config, msg, room, event)
await command.process()
async def invite(self, room, event):
"""Callback for when an invite is received. Join the room specified in the invite"""
logger.debug(f"Got invite to {room.room_id} from {event.sender}.")
# Attempt to join 3 times before giving up
for attempt in range(3):
result = await self.client.join(room.room_id)
if type(result) == JoinError:
logger.error(
f"Error joining room {room.room_id} (attempt %d): %s",
attempt,
result.message,
)
else:
break
else:
logger.error("Unable to join room: %s", room.room_id)
# Successfully joined room
logger.info(f"Joined {room.room_id}")
async def _reaction(
self, room: MatrixRoom, event: UnknownEvent, reacted_to_id: str
):
"""A reaction was sent to one of our messages. Let's send a reply acknowledging it.
Args:
room: The room the reaction was sent in.
event: The reaction event.
reacted_to_id: The event ID that the reaction points to.
"""
logger.debug(f"Got reaction to {room.room_id} from {event.sender}.")
# Get the original event that was reacted to
event_response = await self.client.room_get_event(room.room_id, reacted_to_id)
if isinstance(event_response, RoomGetEventError):
logger.warning(
"Error getting event that was reacted to (%s)", reacted_to_id
)
return
reacted_to_event = event_response.event
# Only acknowledge reactions to events that we sent
if reacted_to_event.sender != self.config.user_id:
return
# Send a message acknowledging the reaction
reaction_sender_pill = make_pill(event.sender)
reaction_content = (
event.source.get("content", {}).get("m.relates_to", {}).get("key")
)
message = (
f"{reaction_sender_pill} reacted to this event with `{reaction_content}`!"
)
await send_text_to_room(
self.client,
room.room_id,
message,
reply_to_event_id=reacted_to_id,
)
async def unknown(self, room: MatrixRoom, event: UnknownEvent):
"""Callback for when an event with a type that is unknown to matrix-nio is received.
Currently this is used for reaction events, which are not specced.
Args:
room: The room the reaction was sent in.
event: The reaction event.
"""
if event.type == "m.reaction":
# Get the ID of the event this was a reaction to
relation_dict = event.source.get("content", {}).get("m.relates_to", {})
reacted_to = relation_dict.get("event_id")
if reacted_to and relation_dict.get("rel_type") == "m.annotation":
await self._reaction(room, event, reacted_to)
logger.debug(
f"Got unknown event with type to {event.type} from {event.sender} in {room.room_id}."
)