add python async
This commit is contained in:
parent
87ffdd7bb3
commit
d58ea40593
15 changed files with 297 additions and 91 deletions
|
|
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||
[project]
|
||||
name = "smal"
|
||||
version = "0.0.1"
|
||||
requires-python = ">=3.11"
|
||||
requires-python = ">=3.10"
|
||||
description = "smal - simple matrix application library"
|
||||
authors = [{ name = "saces" }]
|
||||
license = "AGPL-3.0-only"
|
||||
|
|
@ -16,8 +16,10 @@ classifiers = [
|
|||
]
|
||||
|
||||
dependencies = [
|
||||
"asyncio",
|
||||
"cffi>=2.0.0",
|
||||
"click",
|
||||
"pygomx>=0.0.2"
|
||||
]
|
||||
|
||||
[tool.setuptools.package-dir]
|
||||
|
|
@ -38,3 +40,4 @@ mxclearaccount = "pymxutils.mxutils:clearaccount"
|
|||
mxserverinfo = "pymxutils.mxutils:serverinfo"
|
||||
smalsetup = "smal.smalsetup:smalsetup"
|
||||
demobot = "demobot:main"
|
||||
simplebot = "demobot.simple:main"
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ DEFAULT_PREFIX = "!"
|
|||
|
||||
class DemoBot(SMALBot):
|
||||
|
||||
def on_sys(self, ntf):
|
||||
async def on_sys(self, ntf):
|
||||
print("Got a system notification: ", ntf)
|
||||
|
||||
def on_event(self, evt):
|
||||
async def on_event(self, evt):
|
||||
print("Got an event: ", evt)
|
||||
|
||||
def on_message(self, msg):
|
||||
async def on_message(self, msg):
|
||||
|
||||
if msg["type"] != "m.room.message":
|
||||
# not a room message
|
||||
|
|
@ -46,7 +46,7 @@ class DemoBot(SMALBot):
|
|||
|
||||
if msg["content"]["body"] == "!leave":
|
||||
logger.info(f"leaving room {msg['roomid']}")
|
||||
self.leaveroom(msg["roomid"])
|
||||
await self.leaveroom(msg["roomid"])
|
||||
return
|
||||
|
||||
if msg["content"]["body"].startswith("!echo"):
|
||||
|
|
@ -57,32 +57,30 @@ class DemoBot(SMALBot):
|
|||
txt = "Empty text? Are you kidding me?"
|
||||
|
||||
if msg["is_direct"]:
|
||||
self.sendmessage(msg["roomid"], txt)
|
||||
await self.sendmessage(msg["roomid"], txt)
|
||||
else:
|
||||
self.sendmessagereply(msg["roomid"], msg["id"], msg["sender"], txt)
|
||||
await self.sendmessagereply(
|
||||
msg["roomid"], msg["id"], msg["sender"], txt
|
||||
)
|
||||
return
|
||||
|
||||
logger.info(f"ignored a message: {msg}")
|
||||
|
||||
def listjoinedrooms(self):
|
||||
roomlist = self.joinedrooms()
|
||||
async def on_startup_run(self):
|
||||
roomlist = await self.joinedrooms()
|
||||
for room in roomlist:
|
||||
if room["is_direct"]:
|
||||
txt = "Hey, I'm back for secret talk :)"
|
||||
else:
|
||||
txt = "I'm back online."
|
||||
self.sendnotice(room["roomid"], txt)
|
||||
await self.sendnotice(room["roomid"], txt)
|
||||
|
||||
|
||||
def main():
|
||||
# create and initialize the bot
|
||||
bot = DemoBot(DEFAULT_PREFIX)
|
||||
|
||||
# the bot's matrix client is ready to use now
|
||||
# request the list of joined rooms
|
||||
bot.listjoinedrooms()
|
||||
|
||||
# start syncing forever (listen for incommmig messages/events)
|
||||
# start the asyncio event loop and sync forever (listen for incommmig messages/events)
|
||||
bot.run()
|
||||
|
||||
|
||||
|
|
|
|||
1
smal/src/demobot/simple/__init__.py
Normal file
1
smal/src/demobot/simple/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .demobot import main
|
||||
4
smal/src/demobot/simple/__main__.py
Normal file
4
smal/src/demobot/simple/__main__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import sys
|
||||
from .demobot import main
|
||||
|
||||
sys.exit(main())
|
||||
90
smal/src/demobot/simple/demobot.py
Normal file
90
smal/src/demobot/simple/demobot.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import logging
|
||||
from smal.simple.bot import SMALBot
|
||||
|
||||
# setup logging, we want timestamps
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)s %(name)s - %(funcName)s: %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(level=logging.INFO)
|
||||
|
||||
|
||||
DEFAULT_PREFIX = "!"
|
||||
|
||||
|
||||
class SimpleDemoBot(SMALBot):
|
||||
|
||||
def on_sys(self, ntf):
|
||||
print("Got a system notification: ", ntf)
|
||||
|
||||
def on_event(self, evt):
|
||||
print("Got an event: ", evt)
|
||||
|
||||
def on_message(self, msg):
|
||||
|
||||
if msg["type"] != "m.room.message":
|
||||
# not a room message
|
||||
logger.error(f"not a room message: {msg}")
|
||||
return
|
||||
|
||||
if msg["sender"] == self.UserID:
|
||||
# ignore own messages
|
||||
logger.info(f"ignore own message: {msg}")
|
||||
return
|
||||
|
||||
if "msgtype" in msg["content"].keys() and msg["content"]["msgtype"] != "m.text":
|
||||
# only react to messages, not emotes
|
||||
logger.debug(f"ignore unknown message type: {msg}")
|
||||
return
|
||||
|
||||
if msg["content"]["body"] == "!stop":
|
||||
logger.info("stopping the bot")
|
||||
self.stop()
|
||||
return
|
||||
|
||||
if msg["content"]["body"] == "!leave":
|
||||
logger.info(f"leaving room {msg['roomid']}")
|
||||
self.leaveroom(msg["roomid"])
|
||||
return
|
||||
|
||||
if msg["content"]["body"].startswith("!echo"):
|
||||
|
||||
txt = msg["content"]["body"][5:].strip()
|
||||
|
||||
if txt == "":
|
||||
txt = "Empty text? Are you kidding me?"
|
||||
|
||||
if msg["is_direct"]:
|
||||
self.sendmessage(msg["roomid"], txt)
|
||||
else:
|
||||
self.sendmessagereply(msg["roomid"], msg["id"], msg["sender"], txt)
|
||||
return
|
||||
|
||||
logger.info(f"ignored a message: {msg}")
|
||||
|
||||
def listjoinedrooms(self):
|
||||
roomlist = self.joinedrooms()
|
||||
for room in roomlist:
|
||||
if room["is_direct"]:
|
||||
txt = "Hey, I'm back for secret talk :)"
|
||||
else:
|
||||
txt = "I'm back online."
|
||||
self.sendnotice(room["roomid"], txt)
|
||||
|
||||
|
||||
def main():
|
||||
# create and initialize the bot
|
||||
bot = SimpleDemoBot(DEFAULT_PREFIX)
|
||||
|
||||
# the bot's matrix client is ready to use now
|
||||
# request the list of joined rooms
|
||||
bot.listjoinedrooms()
|
||||
|
||||
# start syncing forever (listen for incommmig messages/events)
|
||||
bot.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from .pygomx import _MXClient
|
||||
from pygomx.client import _AsyncClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -11,8 +12,35 @@ logger = logging.getLogger(__name__)
|
|||
"""
|
||||
|
||||
|
||||
class SMALApp(_MXClient):
|
||||
""" """
|
||||
class SMALApp(_AsyncClient):
|
||||
"""
|
||||
|
||||
implement 'async def self.on_startup()'
|
||||
async_client is logged in & ready.
|
||||
time to setup extra things & hooks not covered by this class
|
||||
sync_loop will not start til we return
|
||||
|
||||
implement 'async def self.on_startup_run()'
|
||||
async_client is logged in & ready.
|
||||
this will not wait for return
|
||||
do your even long running startup code here
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def run(self):
|
||||
asyncio.run(self.main_loop())
|
||||
|
||||
async def main_loop(self):
|
||||
if hasattr(self, "on_startup") and callable(self.on_startup):
|
||||
await self.on_startup()
|
||||
|
||||
if hasattr(self, "on_startup_run") and callable(self.on_startup_run):
|
||||
await asyncio.ensure_future(self.on_startup_run())
|
||||
|
||||
await self._sync()
|
||||
|
||||
def stop(self):
|
||||
self._stopsync()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from .app import SMALApp
|
||||
|
||||
|
|
@ -18,22 +17,16 @@ class SMALBot(SMALApp):
|
|||
super().__init__()
|
||||
self._sigil = sigil
|
||||
|
||||
def run(self):
|
||||
self._sync()
|
||||
|
||||
def stop(self):
|
||||
self._stopsync()
|
||||
|
||||
def sendmessage(self, roomid, text):
|
||||
async def sendmessage(self, roomid, text):
|
||||
data = {}
|
||||
data["roomid"] = roomid
|
||||
data["content"] = {}
|
||||
data["content"]["body"] = text
|
||||
data["content"]["msgtype"] = "m.text"
|
||||
|
||||
self._sendmessage(data)
|
||||
await self._sendmessage(data)
|
||||
|
||||
def sendmessagereply(self, roomid, msgid, mxid, text):
|
||||
async def sendmessagereply(self, roomid, msgid, mxid, text):
|
||||
data = {}
|
||||
data["roomid"] = roomid
|
||||
data["content"] = {}
|
||||
|
|
@ -47,13 +40,13 @@ class SMALBot(SMALApp):
|
|||
data["content"]["m.relates_to"]["m.in_reply_to"] = {}
|
||||
data["content"]["m.relates_to"]["m.in_reply_to"]["event_id"] = msgid
|
||||
|
||||
self._sendmessage(data)
|
||||
await self._sendmessage(data)
|
||||
|
||||
def sendnotice(self, roomid, text):
|
||||
async def sendnotice(self, roomid, text):
|
||||
data = {}
|
||||
data["roomid"] = roomid
|
||||
data["content"] = {}
|
||||
data["content"]["body"] = text
|
||||
data["content"]["msgtype"] = "m.notice"
|
||||
|
||||
self._sendmessage(data)
|
||||
await self._sendmessage(data)
|
||||
|
|
|
|||
0
smal/src/smal/py.typed
Normal file
0
smal/src/smal/py.typed
Normal file
|
|
@ -1,141 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from _pygomx import lib, ffi
|
||||
import json
|
||||
from .errors import APIError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def checkApiError(cstr):
|
||||
result = ffi.string(cstr).decode("utf-8")
|
||||
lib.FreeCString(cstr)
|
||||
|
||||
if result.startswith("ERR:"):
|
||||
raise APIError(result)
|
||||
|
||||
if result == "SUCCESS.":
|
||||
return
|
||||
|
||||
logger.debug(result)
|
||||
|
||||
result_dict = json.loads(result)
|
||||
return result_dict
|
||||
|
||||
|
||||
class _MXClient:
|
||||
"""
|
||||
core binding
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._createMXClient()
|
||||
# create a c-handle for self and keep it alive
|
||||
self._ffi_selfhandle = ffi.new_handle(self)
|
||||
|
||||
r = lib.apiv0_set_on_event_handler(
|
||||
self.client_id, on_event_callback, self._ffi_selfhandle
|
||||
)
|
||||
result = ffi.string(r)
|
||||
lib.FreeCString(r)
|
||||
if result.startswith(b"ERR:"):
|
||||
raise APIError(result)
|
||||
|
||||
r = lib.apiv0_set_on_message_handler(
|
||||
self.client_id, on_message_callback, self._ffi_selfhandle
|
||||
)
|
||||
result = ffi.string(r)
|
||||
lib.FreeCString(r)
|
||||
if result.startswith(b"ERR:"):
|
||||
raise APIError(result)
|
||||
|
||||
r = lib.apiv0_set_on_sys_handler(
|
||||
self.client_id, on_sys_callback, self._ffi_selfhandle
|
||||
)
|
||||
result = ffi.string(r)
|
||||
lib.FreeCString(r)
|
||||
if result.startswith(b"ERR:"):
|
||||
raise APIError(result)
|
||||
|
||||
def _createMXClient(self):
|
||||
r = lib.apiv0_createclient_pass(b".mxpass", b".", b"*", b"*", b"*")
|
||||
|
||||
result = ffi.string(r)
|
||||
lib.FreeCString(r)
|
||||
if result.startswith(b"ERR:"):
|
||||
raise APIError(result)
|
||||
|
||||
result_dict = json.loads(result)
|
||||
self.client_id = result_dict["id"]
|
||||
self.UserID = result_dict["userid"]
|
||||
self.DeviceID = result_dict["deviceid"]
|
||||
|
||||
def _sync(self):
|
||||
r = lib.apiv0_startclient(self.client_id)
|
||||
checkApiError(r)
|
||||
|
||||
def _stopsync(self):
|
||||
r = lib.apiv0_stopclient(self.client_id)
|
||||
checkApiError(r)
|
||||
|
||||
def _sendmessage(self, data_dict):
|
||||
data = json.dumps(data_dict).encode(encoding="utf-8")
|
||||
r = lib.apiv0_sendmessage(self.client_id, data)
|
||||
result = checkApiError(r)
|
||||
return result
|
||||
|
||||
def leaveroom(self, roomid):
|
||||
r = lib.apiv0_leaveroom(self.client_id, roomid.encode(encoding="utf-8"))
|
||||
checkApiError(r)
|
||||
|
||||
def joinedrooms(self):
|
||||
r = lib.apiv0_joinedrooms(self.client_id)
|
||||
return checkApiError(r)
|
||||
|
||||
def _createroom(self, data_dict):
|
||||
data = json.dumps(data_dict).encode(encoding="utf-8")
|
||||
r = lib.apiv0_createroom(self.client_id, data)
|
||||
return checkApiError(r)
|
||||
|
||||
def process_event(self, evt):
|
||||
if hasattr(self, "on_event") and callable(self.on_event):
|
||||
self.on_event(evt)
|
||||
else:
|
||||
logger.warn(f"got event but on_event not declared: {evt}")
|
||||
|
||||
def process_message(self, msg):
|
||||
if hasattr(self, "on_message") and callable(self.on_message):
|
||||
self.on_message(msg)
|
||||
else:
|
||||
logger.warn(f"got message but on_message not declared: {msg}")
|
||||
|
||||
def process_sys(self, ntf):
|
||||
if hasattr(self, "on_sys") and callable(self.on_sys):
|
||||
self.on_sys(ntf)
|
||||
else:
|
||||
logger.warn(f"got systen notification but on_sys not declared: {ntf}")
|
||||
|
||||
|
||||
@ffi.callback("void(char*, void*)")
|
||||
def on_event_callback(evt, pobj):
|
||||
cli = ffi.from_handle(pobj)
|
||||
e = ffi.string(evt).decode("utf-8")
|
||||
evt = json.loads(e)
|
||||
cli.process_event(evt)
|
||||
|
||||
|
||||
@ffi.callback("void(char*, void*)")
|
||||
def on_message_callback(msg, pobj):
|
||||
cli = ffi.from_handle(pobj)
|
||||
m = ffi.string(msg).decode("utf-8")
|
||||
msg = json.loads(m)
|
||||
cli.process_message(msg)
|
||||
|
||||
|
||||
@ffi.callback("void(char*, void*)")
|
||||
def on_sys_callback(msg, pobj):
|
||||
cli = ffi.from_handle(pobj)
|
||||
m = ffi.string(msg).decode("utf-8")
|
||||
sys = json.loads(m)
|
||||
cli.process_sys(sys)
|
||||
18
smal/src/smal/simple/app.py
Normal file
18
smal/src/smal/simple/app.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from pygomx.simple import _SimpleClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class SMALApp(_SimpleClient):
|
||||
""" """
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
58
smal/src/smal/simple/bot.py
Normal file
58
smal/src/smal/simple/bot.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
from .app import SMALApp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class SMALBot(SMALApp):
|
||||
""" """
|
||||
|
||||
def __init__(self, sigil):
|
||||
super().__init__()
|
||||
self._sigil = sigil
|
||||
|
||||
def run(self):
|
||||
self._sync()
|
||||
|
||||
def stop(self):
|
||||
self._stopsync()
|
||||
|
||||
def sendmessage(self, roomid, text):
|
||||
data = {}
|
||||
data["roomid"] = roomid
|
||||
data["content"] = {}
|
||||
data["content"]["body"] = text
|
||||
data["content"]["msgtype"] = "m.text"
|
||||
|
||||
self._sendmessage(data)
|
||||
|
||||
def sendmessagereply(self, roomid, msgid, mxid, text):
|
||||
data = {}
|
||||
data["roomid"] = roomid
|
||||
data["content"] = {}
|
||||
data["content"]["body"] = text
|
||||
data["content"]["msgtype"] = "m.text"
|
||||
data["content"]["m.mentions"] = {}
|
||||
data["content"]["m.mentions"]["user_ids"] = [
|
||||
mxid,
|
||||
]
|
||||
data["content"]["m.relates_to"] = {}
|
||||
data["content"]["m.relates_to"]["m.in_reply_to"] = {}
|
||||
data["content"]["m.relates_to"]["m.in_reply_to"]["event_id"] = msgid
|
||||
|
||||
self._sendmessage(data)
|
||||
|
||||
def sendnotice(self, roomid, text):
|
||||
data = {}
|
||||
data["roomid"] = roomid
|
||||
data["content"] = {}
|
||||
data["content"]["body"] = text
|
||||
data["content"]["msgtype"] = "m.notice"
|
||||
|
||||
self._sendmessage(data)
|
||||
Loading…
Add table
Add a link
Reference in a new issue