Create basic Bot functionality

Add decorator for adding listeners to the bot object
master
DustyP 6 years ago
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…
Cancel
Save