Skip to content
Open
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
33 changes: 33 additions & 0 deletions examples/member.py
Comment thread
chrisdedman marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from matrix import Bot

bot = Bot()


@bot.command()
async def profile(ctx):
member = ctx.member

name = await member.get_display_name()
avatar = await member.get_avatar_url()
level = await member.get_room_power_level(ctx.room)
presence = await member.get_presence()
has_permission = await member.has_room_permission(ctx.room, "ban")
has_event_permission = await member.has_event_permission(
ctx.room, "m.room.history_visibility"
)

await ctx.reply(
f"Welcome {member.mention()}!\n"
f"Display name: {name}\n"
f"Avatar URL: {avatar}\n"
f"Power level: {level}\n"
f"Presence: {presence.presence}\n"
f"last_active_ago: {presence.last_active_ago}\n"
f"currently_active: {presence.currently_active}\n"
f"status_msg: {presence.status_msg}\n"
f"Has permission to ban users: {has_permission}\n"
f"Has permission to see history: {has_event_permission}"
)


bot.start(config="config.yaml")
2 changes: 2 additions & 0 deletions matrix/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .message import Message
from .room import Room
from .types import File, Image
from .member import Member

if TYPE_CHECKING:
from .bot import Bot # pragma: no cover
Expand All @@ -23,6 +24,7 @@ def __init__(self, bot: "Bot", room: Room, event: Event):
self.bot = bot
self.room = room
self.event = event
self.member = Member(event.sender, bot.client)

self.body: str = getattr(event, "body", "")
self.sender: str = event.sender
Expand Down
193 changes: 193 additions & 0 deletions matrix/member.py
Comment thread
chrisdedman marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
from matrix.room import Room
from nio import AsyncClient
from matrix.api import matrix_call
from dataclasses import dataclass
from typing import Any


@dataclass
class MemberProfile:
displayname: str | None
avatar_url: str | None
other_info: dict[Any, Any]


@dataclass
class MemberPresence:
user_id: str
presence: str
last_active_ago: int | None
currently_active: bool | None
status_msg: str | None


class Member:
def __init__(self, user_id: str, client: AsyncClient) -> None:
self._user_id: str = user_id
self._client: AsyncClient = client

def __str__(self) -> str:
return self.mention()

@property
def user_id(self) -> str:
return self._user_id

def mention(self) -> str:
"""Get a Markdown-formatted mention link (matrix.to pill) for this member.

## Example

```python
await ctx.reply(f"Welcome {member.mention()}!")
```
"""
return f"[{self._user_id}](https://matrix.to/#/{self._user_id})"

async def get_profile(self) -> MemberProfile:
"""Get the profile information for this member.

## Example

```python
profile = await member.get_profile()
for key, value in profile.other_info.items():
print(f"{key}: {value}")
```
"""
profile = await matrix_call(
self._client.get_profile(self._user_id),
error_message=f"Failed to get profile for user {self._user_id}",
)

return MemberProfile(
displayname=profile.displayname,
avatar_url=profile.avatar_url,
other_info=profile.other_info,
)

async def get_display_name(self) -> str | None:
"""Get the display name for this member.

## Example

```python
display_name = await member.get_display_name()
print(f"Display name: {display_name}")
```
"""
response = await matrix_call(
self._client.get_displayname(self._user_id),
error_message=f"Failed to get display name for user {self._user_id}",
)

displayname: str | None = response.displayname
return displayname

async def get_avatar_url(self) -> str | None:
"""Get the avatar URL for this member.

## Example

```python
avatar_url = await member.get_avatar_url()
print(f"Avatar URL: {avatar_url}")
```
"""
response = await matrix_call(
self._client.get_avatar(self._user_id),
error_message=f"Failed to get avatar for user {self._user_id}",
)
if not response.avatar_url:
return None

avatar_url: str | None = await self._client.mxc_to_http(response.avatar_url)
return avatar_url

async def get_presence(self) -> MemberPresence:
"""Get the presence status for this member.

## Example

```python
presence = await member.get_presence()
print(f"State: {presence.presence}")
print(f"Currently active: {presence.currently_active}")
print(f"Last active: {presence.last_active_ago} ms ago")
print(f"Status: {presence.status_msg}")
```
"""
presence = await matrix_call(
self._client.get_presence(self._user_id),
error_message=f"Failed to get presence for user {self._user_id}",
)

return MemberPresence(
user_id=presence.user_id,
presence=presence.presence,
last_active_ago=presence.last_active_ago,
currently_active=presence.currently_active,
status_msg=presence.status_msg,
)

async def get_room_power_level(self, room: Room) -> int:
"""Get the power level for this member in a specific room.

## Example

```python
level = await member.get_room_power_level(ctx.room)
print(f"Power level in room {room.room_id}: {level}")
```
"""
power_level = await room.get_state_event("m.room.power_levels", "")

content = power_level.content
users = content.get("users", {})
default = content.get("users_default", 0)

return int(users.get(self._user_id, default))

async def has_room_permission(self, room: Room, permission: str) -> bool:
Comment thread
chrisdedman marked this conversation as resolved.
"""Check if this member has a specific permission in a room.

## Example

```python
has_permission = await member.has_room_permission(ctx.room, "ban")
print(f"Has ban permission: {has_permission}")
```
"""
power_levels_event = await room.get_state_event("m.room.power_levels", "")

content = power_levels_event.content
if permission not in content:
return False # Permission not defined in the room's power levels

users = content.get("users", {})
default = content.get("users_default", 0)
power_level = int(users.get(self._user_id, default))
required_level = content.get(permission)

return bool(power_level >= required_level)

async def has_event_permission(self, room: Room, event_type: str) -> bool:
Comment thread
chrisdedman marked this conversation as resolved.
"""Check if this member has permission to send a specific event type in a room.

## Example

```python
can_send_message = await member.has_event_permission(ctx.room, "m.room.message")
print(f"Can send messages: {can_send_message}")
```
"""
power_level = await self.get_room_power_level(room)
Comment thread
chrisdedman marked this conversation as resolved.
power_levels_event = await room.get_state_event("m.room.power_levels", "")

content = power_levels_event.content
events = content.get("events", {})

if event_type not in events:
return False

return bool(power_level >= events[event_type])
20 changes: 17 additions & 3 deletions matrix/room.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from typing import Any

from nio import AsyncClient, MatrixRoom, Event
from typing import Any, Union

from nio import (
AsyncClient,
MatrixRoom,
Event,
RoomGetStateEventResponse,
RoomGetStateEventError,
)

from matrix.api import matrix_call
from matrix.message import Message
Expand Down Expand Up @@ -97,6 +103,14 @@ def encrypted(self) -> bool:
"""Whether the room is encrypted."""
return self._matrix_room.encrypted # type: ignore[no-any-return]

async def get_state_event(
self, event_type: str, state_key: str = ""
) -> Union[RoomGetStateEventResponse, RoomGetStateEventError]:
return await matrix_call(
self._client.room_get_state_event(self.room_id, event_type, state_key),
error_message=f"Failed to get state event for room {self._matrix_room.room_id}",
)

async def send(
self,
content: str | None = None,
Expand Down
Loading
Loading