Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions matrix/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,15 +337,9 @@ def wrapper(func: ErrorCallback) -> Callable:
return wrapper

def get_room(self, room_id: str) -> Room:
"""
Retrieve a Room instance based on the room_id.

:param room_id: The ID of the room to retrieve.
:type room_id: str
:return: An instance of the Room class.
:rtype: Room
"""
return Room(room_id=room_id, bot=self)
"""Retrieve a Room instance based on the room_id."""
matrix_room = self.client.rooms[room_id]
return Room(matrix_room=matrix_room, client=self.client)

def _auto_register_events(self) -> None:
for attr in dir(self):
Expand Down Expand Up @@ -390,8 +384,9 @@ async def _process_commands(self, room: MatrixRoom, event: Event) -> None:

await ctx.command(ctx)

async def _build_context(self, room: MatrixRoom, event: Event) -> Context:
async def _build_context(self, matrix_room: MatrixRoom, event: Event) -> Context:
"""Builds the base context and extracts the command from the event"""
room = self.get_room(matrix_room.room_id)
ctx = Context(bot=self, room=room, event=event)

if not self.prefix or not ctx.body.startswith(self.prefix):
Expand Down
164 changes: 164 additions & 0 deletions matrix/content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from markdown import markdown
from typing import Any


class BaseMessageContent(ABC):
"""Base class for outgoing message payloads."""

msgtype: str

@abstractmethod
def build(self) -> dict[str, Any]:
pass


@dataclass
class TextContent(BaseMessageContent):
msgtype = "m.text"
body: str

def build(self) -> dict:
return {"msgtype": self.msgtype, "body": self.body}


@dataclass
class MarkdownMessage(TextContent):
def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.body,
"format": "org.matrix.custom.html",
"formatted_body": markdown(self.body, extensions=["nl2br"]),
}


@dataclass
class NoticeContent(TextContent):
msgtype = "m.notice"


@dataclass
class ReplyContent(TextContent):
reply_to_event_id: str

def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.body,
"m.relates_to": {"m.in_reply_to": {"event_id": self.reply_to_event_id}},
}


@dataclass
class FileContent(BaseMessageContent):
msgtype = "m.file"
filename: str
url: str
mimetype: str

def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.filename,
"url": self.url,
"info": {"mimetype": self.mimetype},
}


@dataclass
class ImageContent(BaseMessageContent):
msgtype = "m.image"
filename: str
url: str
mimetype: str
height: int = 0
width: int = 0

def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.filename,
"url": self.url,
"info": {
"mimetype": self.mimetype,
"h": self.height,
"w": self.width,
},
}


@dataclass
class AudioContent(BaseMessageContent):
msgtype = "m.audio"
filename: str
url: str
mimetype: str
duration: int = 0

def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.filename,
"url": self.url,
"info": {
"mimetype": self.mimetype,
"duration": self.duration,
},
}


@dataclass
class VideoContent(BaseMessageContent):
msgtype = "m.video"
filename: str
url: str
mimetype: str
height: int = 0
width: int = 0
duration: int = 0

def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.filename,
"url": self.url,
"info": {
"mimetype": self.mimetype,
"h": self.height,
"w": self.width,
"duration": self.duration,
},
}


@dataclass
class LocationContent(BaseMessageContent):
msgtype = "m.location"
geo_uri: str
description: str = ""

def build(self) -> dict:
return {
"msgtype": self.msgtype,
"body": self.description or self.geo_uri,
"geo_uri": self.geo_uri,
}


@dataclass
class ReactionContent(BaseMessageContent):
"""For sending reactions to an event."""

event_id: str
emoji: str

def build(self) -> dict:
return {
"m.relates_to": {
"rel_type": "m.annotation",
"event_id": self.event_id,
"key": self.emoji,
}
}
27 changes: 13 additions & 14 deletions matrix/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .errors import MatrixError
from .message import Message
from .room import Room

if TYPE_CHECKING:
from .bot import Bot # pragma: no cover
Expand All @@ -20,14 +21,14 @@ class Context:
:param bot: The bot instance executing the command.
:type bot: Bot
:param room: The Matrix room where the event occurred.
:type room: MatrixRoom
:type room: Room
:param event: The event that triggered the command or message.
:type event: Event

:raises MatrixError: If a Matrix operation fails.
"""

def __init__(self, bot: "Bot", room: MatrixRoom, event: Event):
def __init__(self, bot: "Bot", room: Room, event: Event):
self.bot = bot
self.room = room
self.event = event
Expand All @@ -39,7 +40,7 @@ def __init__(self, bot: "Bot", room: MatrixRoom, event: Event):
self.room_id: str = room.room_id
self.room_name: str = room.name

# Command metdata
# Command metadata
self.prefix: str = bot.prefix
self.command: Optional[Command] = None
self.subcommand: Optional[Command] = None
Expand Down Expand Up @@ -68,19 +69,17 @@ def logger(self) -> Any:
"""Logger for instance specific to the current room or event."""
return self.bot.log.getChild(self.room_id)

async def reply(self, message: str) -> None:
"""
Send a message to the Matrix room.

:param message: The message to send.
:type message: str

:return: None
"""
async def reply(
self,
content: str | None,
*,
raw: bool = False,
notice: bool = False,
) -> Message:
"""Send a message to the Matrix room."""

try:
c = Message(self.bot)
await c.send(room_id=self.room_id, message=message)
return await self.room.send(content, raw=raw, notice=notice)
except Exception as e:
raise MatrixError(f"Failed to send message: {e}")

Expand Down
2 changes: 1 addition & 1 deletion matrix/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def register_command(self, cmd: Command) -> Command:
return cmd

async def invoke(self, ctx: "Context") -> None:
if subcommand := ctx.args.pop(0):
if ctx.args and (subcommand := ctx.args.pop(0)):
ctx.subcommand = self.get_command(subcommand)
await ctx.subcommand(ctx)
else:
Expand Down
Loading
Loading