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
|
import asyncio
|
||||||
from typing import Union, Optional, Dict
|
from typing import Union, Optional, Dict, List
|
||||||
|
|
||||||
from .api import API
|
from .api import API
|
||||||
from .room import Room
|
from .room import Room
|
||||||
@ -30,10 +30,16 @@ class Client:
|
|||||||
"rooms": self.process_room_events,
|
"rooms": self.process_room_events,
|
||||||
"groups": self.process_group_events,
|
"groups": self.process_group_events,
|
||||||
}
|
}
|
||||||
self.event_dispatchers: Dict[str, callable] = {}
|
self.event_dispatchers: Dict[str, List[callable]] = {}
|
||||||
self.users = []
|
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:
|
if not password and not token:
|
||||||
raise RuntimeError("Either the password or a token is required")
|
raise RuntimeError("Either the password or a token is required")
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
@ -95,9 +101,10 @@ class Client:
|
|||||||
event_dict["room"] = room
|
event_dict["room"] = room
|
||||||
event = self.process_event(event_dict)
|
event = self.process_event(event_dict)
|
||||||
await room.update_state(event)
|
await room.update_state(event)
|
||||||
handler = self.event_dispatchers.get(event.type)
|
handlers = self.event_dispatchers.get(event.type)
|
||||||
if handler:
|
if handlers:
|
||||||
await self.invoke(handler, event)
|
for handler in handlers:
|
||||||
|
self.loop.create_task(self.invoke(handler, event))
|
||||||
|
|
||||||
# Process ephemeral events
|
# Process ephemeral events
|
||||||
for event in data['ephemeral']['events']:
|
for event in data['ephemeral']['events']:
|
||||||
@ -118,9 +125,10 @@ class Client:
|
|||||||
if event not in room.message_cache:
|
if event not in room.message_cache:
|
||||||
room.message_cache.append(event)
|
room.message_cache.append(event)
|
||||||
if room.read_receipts[self.user_id][1] < event.origin_server_ts:
|
if room.read_receipts[self.user_id][1] < event.origin_server_ts:
|
||||||
handler = self.event_dispatchers.get(event.type)
|
handlers = self.event_dispatchers.get(event.type)
|
||||||
if handler:
|
if handlers:
|
||||||
await self.invoke(handler, event)
|
for handler in handlers:
|
||||||
|
self.loop.create_task(self.invoke(handler, event))
|
||||||
try:
|
try:
|
||||||
await self.mark_event_read(event)
|
await self.mark_event_read(event)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
@ -161,9 +169,15 @@ class Client:
|
|||||||
await handler(event)
|
await handler(event)
|
||||||
|
|
||||||
def register_handler(self, event_type, handler: callable):
|
def register_handler(self, event_type, handler: callable):
|
||||||
|
if not event_type:
|
||||||
|
event_type = handler.__name__.replace('_', '.')
|
||||||
|
|
||||||
if not callable(handler):
|
if not callable(handler):
|
||||||
raise TypeError(f'handler must be a callable not {type(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'):
|
async def mark_event_read(self, event, receipt_type: str = 'm.read'):
|
||||||
from .events import RoomEvent
|
from .events import RoomEvent
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
|
from inspect import isawaitable
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -97,5 +98,13 @@ class MessageRelation:
|
|||||||
event_id: str
|
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():
|
def notification_power_levels_default_factory():
|
||||||
return {'room': 50}
|
return {'room': 50}
|
||||||
|
|||||||
@ -3,24 +3,68 @@ from typing import Union, Optional, Dict
|
|||||||
|
|
||||||
from morpheus.core.client import Client
|
from morpheus.core.client import Client
|
||||||
from morpheus.core.room import Room
|
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
|
from .context import Context
|
||||||
|
|
||||||
|
|
||||||
class Bot(Client):
|
class Bot(Client):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
prefix: Union[str, list, tuple],
|
prefix: Union[str, list, tuple, callable],
|
||||||
homeserver: str = "https://matrixcoding.chat",
|
homeserver: str = "https://matrixcoding.chat",
|
||||||
):
|
):
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
super(Bot, self).__init__(prefix=prefix, homeserver=homeserver)
|
super(Bot, self).__init__(prefix=prefix, homeserver=homeserver)
|
||||||
|
|
||||||
def run(self, user_id: str = None, password: str = None, token: str = None):
|
def run(self, user_id: str = None, password: str = None, token: str = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
||||||
loop = self.loop or asyncio.get_event_loop()
|
loop = loop or self.loop or asyncio.get_event_loop()
|
||||||
loop.run_until_complete(super(Bot, self).run(user_id, password, token))
|
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):
|
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.client import Client
|
||||||
from morpheus.core.room import Room
|
from morpheus.core.room import Room
|
||||||
|
from morpheus.core.events import RoomEvent
|
||||||
|
from morpheus.core.content import ContentBase
|
||||||
|
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
def __init__(self, client: Client, room: Room, prefix: str, sender: str, ):
|
def __init__(self, client: Client, room: Room, calling_prefix: str, sender: str, event: RoomEvent, content: ContentBase, called_with: str, body: str):
|
||||||
self.client: Client
|
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