Create basic Bot functionality
Add decorator for adding listeners to the bot object
This commit is contained in:
parent
d8f53a35be
commit
20629229c0
@ -1,5 +1,5 @@
|
||||
import asyncio
|
||||
from typing import Union, Optional, Dict
|
||||
from typing import Union, Optional, Dict, List
|
||||
|
||||
from .api import API
|
||||
from .room import Room
|
||||
@ -30,10 +30,16 @@ class Client:
|
||||
"rooms": self.process_room_events,
|
||||
"groups": self.process_group_events,
|
||||
}
|
||||
self.event_dispatchers: Dict[str, callable] = {}
|
||||
self.event_dispatchers: Dict[str, List[callable]] = {}
|
||||
self.users = []
|
||||
self.loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
|
||||
async def run(self, user_id: str = None, password: str = None, token: str = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
||||
if loop:
|
||||
self.loop = loop
|
||||
elif not self.loop:
|
||||
self.loop = asyncio.get_event_loop()
|
||||
|
||||
async def run(self, user_id: str = None, password: str = None, token: str = None):
|
||||
if not password and not token:
|
||||
raise RuntimeError("Either the password or a token is required")
|
||||
self.user_id = user_id
|
||||
@ -95,9 +101,10 @@ class Client:
|
||||
event_dict["room"] = room
|
||||
event = self.process_event(event_dict)
|
||||
await room.update_state(event)
|
||||
handler = self.event_dispatchers.get(event.type)
|
||||
if handler:
|
||||
await self.invoke(handler, event)
|
||||
handlers = self.event_dispatchers.get(event.type)
|
||||
if handlers:
|
||||
for handler in handlers:
|
||||
self.loop.create_task(self.invoke(handler, event))
|
||||
|
||||
# Process ephemeral events
|
||||
for event in data['ephemeral']['events']:
|
||||
@ -118,9 +125,10 @@ class Client:
|
||||
if event not in room.message_cache:
|
||||
room.message_cache.append(event)
|
||||
if room.read_receipts[self.user_id][1] < event.origin_server_ts:
|
||||
handler = self.event_dispatchers.get(event.type)
|
||||
if handler:
|
||||
await self.invoke(handler, event)
|
||||
handlers = self.event_dispatchers.get(event.type)
|
||||
if handlers:
|
||||
for handler in handlers:
|
||||
self.loop.create_task(self.invoke(handler, event))
|
||||
try:
|
||||
await self.mark_event_read(event)
|
||||
except RuntimeError as e:
|
||||
@ -161,9 +169,15 @@ class Client:
|
||||
await handler(event)
|
||||
|
||||
def register_handler(self, event_type, handler: callable):
|
||||
if not event_type:
|
||||
event_type = handler.__name__.replace('_', '.')
|
||||
|
||||
if not callable(handler):
|
||||
raise TypeError(f'handler must be a callable not {type(handler)}')
|
||||
self.event_dispatchers[event_type] = handler
|
||||
if event_type in self.event_dispatchers:
|
||||
self.event_dispatchers[event_type].append(handler)
|
||||
else:
|
||||
self.event_dispatchers[event_type] = [handler]
|
||||
|
||||
async def mark_event_read(self, event, receipt_type: str = 'm.read'):
|
||||
from .events import RoomEvent
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List, Dict
|
||||
from inspect import isawaitable
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -97,5 +98,13 @@ class MessageRelation:
|
||||
event_id: str
|
||||
|
||||
|
||||
async def maybe_coroutine(func, *args, **kwargs):
|
||||
f = func(*args, **kwargs)
|
||||
if isawaitable(f):
|
||||
return await f
|
||||
else:
|
||||
return f
|
||||
|
||||
|
||||
def notification_power_levels_default_factory():
|
||||
return {'room': 50}
|
||||
|
||||
@ -3,24 +3,68 @@ from typing import Union, Optional, Dict
|
||||
|
||||
from morpheus.core.client import Client
|
||||
from morpheus.core.room import Room
|
||||
from morpheus.core.utils import maybe_coroutine
|
||||
from morpheus.core.events import RoomEvent
|
||||
from morpheus.core.content import MessageContentBase
|
||||
from .context import Context
|
||||
|
||||
|
||||
class Bot(Client):
|
||||
def __init__(
|
||||
self,
|
||||
prefix: Union[str, list, tuple],
|
||||
prefix: Union[str, list, tuple, callable],
|
||||
homeserver: str = "https://matrixcoding.chat",
|
||||
):
|
||||
self.loop = asyncio.get_event_loop()
|
||||
super(Bot, self).__init__(prefix=prefix, homeserver=homeserver)
|
||||
|
||||
def run(self, user_id: str = None, password: str = None, token: str = None):
|
||||
loop = self.loop or asyncio.get_event_loop()
|
||||
loop.run_until_complete(super(Bot, self).run(user_id, password, token))
|
||||
def run(self, user_id: str = None, password: str = None, token: str = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
||||
loop = loop or self.loop or asyncio.get_event_loop()
|
||||
loop.run_until_complete(super(Bot, self).run(user_id, password, token, loop=loop))
|
||||
|
||||
async def get_context(self, event):
|
||||
async def get_context(self, event: RoomEvent):
|
||||
if not isinstance(event.content, MessageContentBase):
|
||||
return None
|
||||
|
||||
if callable(self.prefix):
|
||||
prefix = await maybe_coroutine(self.prefix, event)
|
||||
elif isinstance(self.prefix, (str, list, tuple)):
|
||||
prefix = self.prefix
|
||||
else:
|
||||
raise RuntimeError('Prefix must be a string, list of strings or callable')
|
||||
|
||||
if isinstance(prefix, str):
|
||||
return self._get_context(event, prefix)
|
||||
elif isinstance(prefix, (list, tuple)):
|
||||
prefixes = tuple(prefix)
|
||||
for prefix in prefixes:
|
||||
try:
|
||||
ctx = self._get_context(event, prefix)
|
||||
if ctx:
|
||||
return ctx
|
||||
except TypeError:
|
||||
raise RuntimeError('Prefix must be a string or list of strings')
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
raise RuntimeError('Prefix must be a string or list of strings')
|
||||
|
||||
@staticmethod
|
||||
def _get_context(event: RoomEvent, prefix: str):
|
||||
if not isinstance(event.content, MessageContentBase):
|
||||
return None
|
||||
|
||||
raw_body = event.content.body
|
||||
if not raw_body.startswith(prefix):
|
||||
return None
|
||||
raw_body = raw_body.lstrip(prefix)
|
||||
called_with, body = raw_body.split(' ', 1)
|
||||
return Context.get_context(event, prefix, called_with, body)
|
||||
|
||||
async def check_event(self, event):
|
||||
ctx = await self.get_context(event)
|
||||
|
||||
def listener(self, name=None):
|
||||
def decorator(func):
|
||||
self.register_handler(name, func)
|
||||
return decorator
|
||||
|
||||
@ -1,6 +1,23 @@
|
||||
from morpheus.core.client import Client
|
||||
from morpheus.core.room import Room
|
||||
from morpheus.core.events import RoomEvent
|
||||
from morpheus.core.content import ContentBase
|
||||
|
||||
|
||||
class Context:
|
||||
def __init__(self, client: Client, room: Room, prefix: str, sender: str, ):
|
||||
self.client: Client
|
||||
def __init__(self, client: Client, room: Room, calling_prefix: str, sender: str, event: RoomEvent, content: ContentBase, called_with: str, body: str):
|
||||
self.client: Client = client
|
||||
self.room: Room = room
|
||||
self.calling_prefix: str = calling_prefix
|
||||
self.sender: str = sender # TODO once the User class is created change this to type User
|
||||
self.event: RoomEvent = event
|
||||
self.content: ContentBase = content
|
||||
self.called_with: str = called_with
|
||||
self.body: str = body
|
||||
|
||||
async def send_text(self, body: str, formatted_body: str = None, format_type: str = 'org.matrix.custom.html'):
|
||||
await self.client.send_text(self.room, body, formatted_body, format_type)
|
||||
|
||||
@classmethod
|
||||
def get_context(cls, event: RoomEvent, calling_prefix: str, called_with: str, body: str):
|
||||
return cls(event.client, event.room, calling_prefix, event.sender, event, event.content, called_with, body)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user