ctx.sender is a plain Matrix ID string. Getting anything beyond the raw ID, (display name, avatar, or power level in the current room), requires making raw API calls manually. Every bot that does permission checks or personalized replies ends up writing the same lookup boilerplate.
The idea is to introduce a Member class that follows the same pattern as Room and Space: sync construction from an ID and a client, with async methods for anything that requires a network call.
Proposed API
class Member:
def __init__(self, user_id: str, client: AsyncClient) -> None: ...
@property
def user_id(self) -> str: ...
async def get_display_name(self) -> str | None: ...
async def get_avatar_url(self) -> str | None: ...
async def get_room_power_level(self, room: Room) -> int: ...
# ctx.member is a sync property — no network call until a method is invoked
@bot.command()
async def profile(ctx):
member = ctx.member
name = await member.get_display_name() or member.user_id
level = await member.get_power_level_for(ctx.room)
await ctx.reply(f"{name} — power level: {level}")
ctx.member would be a sync property returning Member(ctx.sender, ctx.bot.client), matching the construction pattern as Room. get_display_name and get_avatar_url call the Matrix /profile endpoint.
ctx.senderis a plain Matrix ID string. Getting anything beyond the raw ID, (display name, avatar, or power level in the current room), requires making raw API calls manually. Every bot that does permission checks or personalized replies ends up writing the same lookup boilerplate.The idea is to introduce a
Memberclass that follows the same pattern asRoomandSpace: sync construction from an ID and a client, with async methods for anything that requires a network call.Proposed API
ctx.memberwould be a sync property returningMember(ctx.sender, ctx.bot.client), matching the construction pattern asRoom.get_display_nameandget_avatar_urlcall the Matrix/profileendpoint.