Refactor /cmd handling to be more extensible

This commit is contained in:
Tim Clifford 2023-11-04 23:30:02 +00:00 committed by Maze
parent 4de8e87f06
commit 7d6fba5ac7
2 changed files with 102 additions and 9 deletions

View File

@ -179,6 +179,21 @@ class MatrixClient(nio.AsyncClient):
if host in ("127.0.0.1", "localhost", "::1"): if host in ("127.0.0.1", "localhost", "::1"):
proxy = None proxy = None
# self is passed in explicitly, so that it will also be passed to
# handlers in the user config
self.cmd_handler_map = {
"me ": MatrixClient.handle_cmd_emote,
}
try:
self.cmd_handler_map = {
**self.cmd_handler_map,
**backend.settings.Commands.get_cmd_handler_map(),
}
except (AttributeError, KeyError):
# make sure we don't break older configs
pass
super().__init__( super().__init__(
homeserver = homeserver, homeserver = homeserver,
user = user, user = user,
@ -618,22 +633,40 @@ class MatrixClient(nio.AsyncClient):
display_name_mentions: Optional[Dict[str, str]] = None, # {id: name} display_name_mentions: Optional[Dict[str, str]] = None, # {id: name}
reply_to_event_id: Optional[str] = None, reply_to_event_id: Optional[str] = None,
) -> None: ) -> None:
"""Send a markdown `m.text` or `m.notice` (with `/me`) message .""" if text.startswith("//") or text.startswith(r"\/"):
await self._send_text(room_id, text[1:], display_name_mentions, reply_to_event_id)
elif text.startswith("/"):
for k,v in self.cmd_handler_map.items():
if text.startswith("/"+k):
await v(self, room_id, text[len("/"+k):], display_name_mentions, reply_to_event_id)
break
else:
await self.send_fake_notice(
room_id,
r"That command was not recognised. To send a message starting with /, use //",
)
else:
await self._send_text(room_id, text, display_name_mentions, reply_to_event_id)
async def _send_text(
self,
room_id: str,
text: str,
display_name_mentions: Optional[Dict[str, str]] = None, # {id: name}
reply_to_event_id: Optional[str] = None,
emote: bool = False,
) -> None:
"""Send a markdown `m.text` or `m.emote` message ."""
from_md = partial( from_md = partial(
HTML.from_markdown, display_name_mentions=display_name_mentions, HTML.from_markdown, display_name_mentions=display_name_mentions,
) )
escape = False
if text.startswith("//") or text.startswith(r"\/"):
escape = True
text = text[1:]
content: Dict[str, Any] content: Dict[str, Any]
if text.startswith("/me ") and not escape: if emote:
event_type = nio.RoomMessageEmote event_type = nio.RoomMessageEmote
text = text[len("/me "):]
content = {"body": text, "msgtype": "m.emote"} content = {"body": text, "msgtype": "m.emote"}
to_html = from_md(text, inline=True, outgoing=True) to_html = from_md(text, inline=True, outgoing=True)
echo_body = from_md(text, inline=True) echo_body = from_md(text, inline=True)
@ -699,6 +732,22 @@ class MatrixClient(nio.AsyncClient):
await self._send_message(room_id, content, tx_id) await self._send_message(room_id, content, tx_id)
async def handle_cmd_emote(
self,
room_id: str,
text: str,
display_name_mentions: Optional[Dict[str, str]] = None, # {id: name}
reply_to_event_id: Optional[str] = None,
):
await self._send_text(
room_id,
text,
display_name_mentions,
reply_to_event_id,
emote=True
)
async def toggle_pause_transfer( async def toggle_pause_transfer(
self, room_id: str, uuid: Union[str, UUID], self, room_id: str, uuid: Union[str, UUID],
) -> None: ) -> None:
@ -1023,6 +1072,7 @@ class MatrixClient(nio.AsyncClient):
room_id: str, room_id: str,
transaction_id: UUID, transaction_id: UUID,
event_type: Type[nio.Event], event_type: Type[nio.Event],
fake_event: bool = False,
**event_fields, **event_fields,
) -> None: ) -> None:
"""Register a local model `Event` while waiting for the server. """Register a local model `Event` while waiting for the server.
@ -1056,7 +1106,7 @@ class MatrixClient(nio.AsyncClient):
sender_id = self.user_id, sender_id = self.user_id,
sender_name = our_info.display_name, sender_name = our_info.display_name,
sender_avatar = our_info.avatar_url, sender_avatar = our_info.avatar_url,
is_local_echo = True, is_local_echo = not fake_event,
links = Event.parse_links(content), links = Event.parse_links(content),
**event_fields, **event_fields,
) )
@ -1069,6 +1119,16 @@ class MatrixClient(nio.AsyncClient):
await self.set_room_last_event(room_id, event) await self.set_room_last_event(room_id, event)
async def send_fake_notice(self, room_id, msg):
await self._local_echo(
room_id,
uuid4(),
nio.RoomMessageNotice,
content = msg,
fake_event = True,
)
async def _send_message( async def _send_message(
self, room_id: str, content: dict, transaction_id: UUID, self, room_id: str, content: dict, transaction_id: UUID,
) -> None: ) -> None:

View File

@ -541,3 +541,36 @@ class Keys:
# Sign out checked sessions if any, else sign out all sessions. # Sign out checked sessions if any, else sign out all sessions.
sign_out = ["Alt+S", "Delete"] sign_out = ["Alt+S", "Delete"]
class Commands:
# If you are an advanced user, here you can define new /commands
#
# get_cmd_handler_map should return a dictionary of the form
# {
# "command": command_handling_function,
# "another_command": ...
# }
#
# each command_handling_function should have the signature:
#
# async def command_handling_function(
# matrix_client: moment.backend.MatrixClient,
# room_id: str,
# text: str, # text after the end of /command
# display_name_mentions: Optional[Dict[str, str]] = None, # {id: name}
# reply_to_event_id: Optional[str] = None,
# ) -> None:
# Example:
def get_cmd_handler_map(self):
return {
# "rot13 ": self.handle_cmd_rot13,
}
# @staticmethod
# def handle_cmd_rot13(matrix_client, room_id, text, display_name_mentions, reply_to_event_id):
# import codecs
# matrix_client._send_text(
# room_id, codecs.encode(text, 'rot_13'), display_name_mentions, reply_to_event_id
# )