connect driver-adapter-matcher

This commit is contained in:
yanyongyu 2020-07-11 17:32:03 +08:00
parent ea21d6ffcc
commit d616290626
10 changed files with 280 additions and 20 deletions

View File

@ -1,8 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Dict, Optional
from nonebot.config import Config
class BaseBot(object):
def __init__(self):
def __init__(self, type: str, config: Config, *, websocket=None):
raise NotImplementedError
async def handle_message(self, message: dict):
raise NotImplementedError
async def call_api(self, api: str, data: dict):
raise NotImplementedError
class BaseMessageSegment(dict):
def __init__(self,
d: Optional[Dict[str, Any]] = None,
*,
type_: Optional[str] = None,
data: Optional[Dict[str, str]] = None):
super().__init__()
if isinstance(d, dict) and d.get('type'):
self.update(d)
elif type_:
self.type = type_
self.data = data
else:
raise ValueError('the "type" field cannot be None or empty')
def __str__(self):
raise NotImplementedError
class BaseMessage(list):
def __init__(self, message: str = None):
raise NotImplementedError
def __str__(self):
return ''.join((str(seg) for seg in self))

View File

@ -1,10 +1,40 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from nonebot.adapters import BaseBot
import httpx
from nonebot.event import Event
from nonebot.config import Config
from nonebot.adapters import BaseBot, BaseMessage, BaseMessageSegment
from nonebot.message import handle_event
class Bot(BaseBot):
def __init__(self):
def __init__(self, type_: str, config: Config, *, websocket=None):
if type_ not in ["http", "websocket"]:
raise ValueError("Unsupported connection type")
self.type = type_
self.config = config
self.websocket = websocket
async def handle_message(self, message: dict):
# TODO: convert message into event
event = Event.from_payload(message)
# TODO: Handle Meta Event
await handle_event(self, event)
async def call_api(self, api: str, data: dict):
if self.type == "websocket":
pass
elif self.type == "http":
pass
class MessageSegment(BaseMessageSegment):
pass
class Message(BaseMessage):
pass

View File

@ -4,10 +4,12 @@
from typing import Optional
from ipaddress import IPv4Address
from nonebot.config import Config
class BaseDriver(object):
def __init__(self):
def __init__(self, config: Config):
raise NotImplementedError
@property
@ -34,3 +36,6 @@ class BaseDriver(object):
async def _handle_ws_reverse(self):
raise NotImplementedError
async def _handle_http_api(self):
raise NotImplementedError

View File

@ -7,15 +7,18 @@ from typing import Optional
from ipaddress import IPv4Address
import uvicorn
from starlette.websockets import WebSocketDisconnect
from fastapi import Body, FastAPI, WebSocket
from nonebot.log import logger
from nonebot.config import Config
from nonebot.drivers import BaseDriver
from nonebot.adapters.coolq import Bot as CoolQBot
class Driver(BaseDriver):
def __init__(self, config):
def __init__(self, config: Config):
self._server_app = FastAPI(
debug=config.debug,
openapi_url=None,
@ -25,8 +28,10 @@ class Driver(BaseDriver):
self.config = config
self._server_app.post("/coolq/")(self._handle_http)
self._server_app.websocket("/coolq/ws")(self._handle_ws_reverse)
self._server_app.post("/{adapter}/")(self._handle_http)
self._server_app.post("/{adapter}/http")(self._handle_http)
self._server_app.websocket("/{adapter}/ws")(self._handle_ws_reverse)
self._server_app.websocket("/{adapter}/ws/")(self._handle_ws_reverse)
@property
def server_app(self):
@ -81,11 +86,16 @@ class Driver(BaseDriver):
log_config=LOGGING_CONFIG,
**kwargs)
async def _handle_http(self, data: dict = Body(...)):
async def _handle_http(self, adapter: str, data: dict = Body(...)):
# TODO: Check authorization
logger.debug(f"Received message: {data}")
if adapter == "coolq":
bot = CoolQBot("http", self.config)
await bot.handle_message(data)
return {"status": 200, "message": "success"}
async def _handle_ws_reverse(self, websocket: WebSocket):
async def _handle_ws_reverse(self, adapter: str, websocket: WebSocket):
# TODO: Check authorization
await websocket.accept()
while True:
try:
@ -93,5 +103,11 @@ class Driver(BaseDriver):
except json.decoder.JSONDecodeError as e:
logger.exception(e)
continue
except WebSocketDisconnect:
logger.error("WebSocket Disconnect")
return
logger.debug(f"Received message: {data}")
if adapter == "coolq":
bot = CoolQBot("websocket", self.config, websocket=websocket)
await bot.handle_message(data)

View File

@ -94,7 +94,6 @@ class Matcher:
def _decorator(func: Handler) -> Handler:
@wraps(func)
async def _handler(bot, event: Event, state: dict):
raise PausedException

View File

@ -6,14 +6,20 @@ from nonebot.event import Event
from nonebot.matcher import matchers
async def handle_message(bot, event: Event):
async def handle_event(bot, event: Event):
# TODO: PreProcess
for priority in sorted(matchers.keys()):
for index in range(len(matchers[priority])):
Matcher = matchers[priority][index]
try:
if not Matcher.check_rule(event):
continue
except Exception as e:
logger.error(
f"Rule check failed for matcher {Matcher}. Ignored.")
logger.exception(e)
continue
matcher = Matcher()
if Matcher.temp:
@ -22,5 +28,6 @@ async def handle_message(bot, event: Event):
try:
await matcher.run(bot, event)
except Exception as e:
logger.error(f"Running matcher {matcher} failed.")
logger.exception(e)
return

165
poetry.lock generated
View File

@ -60,7 +60,7 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
optional = false
@ -73,7 +73,7 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
category = "main"
description = "Universal encoding detector for Python 2 and 3"
name = "chardet"
optional = false
@ -161,6 +161,49 @@ reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "HTTP/2 State-Machine based protocol implementation"
name = "h2"
optional = false
python-versions = "*"
version = "3.2.0"
[package.dependencies]
hpack = ">=3.0,<4"
hyperframe = ">=5.2.0,<6"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Pure-Python HPACK header compression"
name = "hpack"
optional = false
python-versions = "*"
version = "3.0.0"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Chromium HSTS Preload list as a Python package and updated daily"
name = "hstspreload"
optional = false
python-versions = ">=3.6"
version = "2020.7.7"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
description = "Turn HTML into equivalent Markdown-structured text."
@ -174,6 +217,24 @@ reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "A minimal low-level HTTP client."
name = "httpcore"
optional = false
python-versions = ">=3.6"
version = "0.9.1"
[package.dependencies]
h11 = ">=0.8,<0.10"
h2 = ">=3.0.0,<4.0.0"
sniffio = ">=1.0.0,<2.0.0"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "A collection of framework independent HTTP protocol utils."
@ -192,7 +253,42 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
category = "main"
description = "The next generation HTTP client."
name = "httpx"
optional = false
python-versions = ">=3.6"
version = "0.13.3"
[package.dependencies]
certifi = "*"
chardet = ">=3.0.0,<4.0.0"
hstspreload = "*"
httpcore = ">=0.9.0,<0.10.0"
idna = ">=2.0.0,<3.0.0"
rfc3986 = ">=1.3,<2"
sniffio = "*"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "HTTP/2 framing layer for Python"
name = "hyperframe"
optional = false
python-versions = "*"
version = "5.2.0"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
@ -383,6 +479,22 @@ reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Validating URI References per RFC 3986"
name = "rfc3986"
optional = false
python-versions = "*"
version = "1.4.0"
[package.extras]
idna2008 = ["idna"]
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Python 2 and 3 compatibility utilities"
@ -396,6 +508,19 @@ reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Sniff out which async library your code is running under"
name = "sniffio"
optional = false
python-versions = ">=3.5"
version = "1.1.0"
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms."
@ -713,7 +838,7 @@ url = "https://mirrors.aliyun.com/pypi/simple"
scheduler = ["apscheduler"]
[metadata]
content-hash = "5dc37a3a06ef422bb885c2f6b09179964e083f2005bf8873349431ebc4508152"
content-hash = "e98c07c170a70d27c0cb15dd6b4cfd10e678a1df55bd3c3bebbbe26035b88a24"
python-versions = "^3.7"
[metadata.files]
@ -757,10 +882,26 @@ h11 = [
{file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"},
]
h2 = [
{file = "h2-3.2.0-py2.py3-none-any.whl", hash = "sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5"},
{file = "h2-3.2.0.tar.gz", hash = "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"},
]
hpack = [
{file = "hpack-3.0.0-py2.py3-none-any.whl", hash = "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89"},
{file = "hpack-3.0.0.tar.gz", hash = "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"},
]
hstspreload = [
{file = "hstspreload-2020.7.7-py3-none-any.whl", hash = "sha256:051a752188c3422558a1302be99b742a2dd9d7568419614104ae7e87233f9d63"},
{file = "hstspreload-2020.7.7.tar.gz", hash = "sha256:3e1b107d6c865fc28f0f023456f193f2e916d14bca5a16c93fe440bef90c7c58"},
]
html2text = [
{file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
{file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"},
]
httpcore = [
{file = "httpcore-0.9.1-py3-none-any.whl", hash = "sha256:9850fe97a166a794d7e920590d5ec49a05488884c9fc8b5dba8561effab0c2a0"},
{file = "httpcore-0.9.1.tar.gz", hash = "sha256:ecc5949310d9dae4de64648a4ce529f86df1f232ce23dcfefe737c24d21dfbe9"},
]
httptools = [
{file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"},
{file = "httptools-0.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4"},
@ -775,6 +916,14 @@ httptools = [
{file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"},
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
]
httpx = [
{file = "httpx-0.13.3-py3-none-any.whl", hash = "sha256:32d930858eab677bc29a742aaa4f096de259f1c78c68a90ad11f5c3c04f08335"},
{file = "httpx-0.13.3.tar.gz", hash = "sha256:3642bd13e90b80ba8a243a730275eb10a4c26ec96f5fc16b87e458d4ab21efae"},
]
hyperframe = [
{file = "hyperframe-5.2.0-py2.py3-none-any.whl", hash = "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40"},
{file = "hyperframe-5.2.0.tar.gz", hash = "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"},
]
idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
@ -869,10 +1018,18 @@ requests = [
{file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
]
rfc3986 = [
{file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"},
{file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"},
]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
sniffio = [
{file = "sniffio-1.1.0-py3-none-any.whl", hash = "sha256:20ed6d5b46f8ae136d00b9dcb807615d83ed82ceea6b2058cecb696765246da5"},
{file = "sniffio-1.1.0.tar.gz", hash = "sha256:8e3810100f69fe0edd463d02ad407112542a11ffdc29f67db2bf3771afb87a21"},
]
snowballstemmer = [
{file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
{file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},

View File

@ -28,6 +28,7 @@ fastapi = "^0.58.1"
uvicorn = "^0.11.5"
pydantic = {extras = ["dotenv"], version = "^1.5.1"}
apscheduler = { version = "^3.6.3", optional = true }
httpx = "^0.13.3"
[tool.poetry.dev-dependencies]
yapf = "^0.30.0"

View File

@ -13,8 +13,6 @@ nonebot.init()
app = nonebot.get_asgi()
nonebot.load_plugins("test_plugins")
print(nonebot.get_loaded_plugins())
print(matchers)
if __name__ == "__main__":
nonebot.run(app="bot:app")

View File

@ -10,4 +10,12 @@ test_matcher = on_message(Rule(), state={"default": 1})
@test_matcher.handle()
async def test_handler(bot, event: Event, state: dict):
print(state)
print("Test Matcher Received:", event)
print("Current State:", state)
state["message1"] = event.get("raw_message")
@test_matcher.receive()
async def test_receive(bot, event: Event, state: dict):
print("Test Matcher Received next time:", event)
print("Current State:", state)