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 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Any, Dict, Optional
from nonebot.config import Config
class BaseBot(object): class BaseBot(object):
def __init__(self): def __init__(self, type: str, config: Config, *, websocket=None):
raise NotImplementedError 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 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- 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): class Bot(BaseBot):
def __init__(self): def __init__(self, type_: str, config: Config, *, websocket=None):
pass 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 typing import Optional
from ipaddress import IPv4Address from ipaddress import IPv4Address
from nonebot.config import Config
class BaseDriver(object): class BaseDriver(object):
def __init__(self): def __init__(self, config: Config):
raise NotImplementedError raise NotImplementedError
@property @property
@ -34,3 +36,6 @@ class BaseDriver(object):
async def _handle_ws_reverse(self): async def _handle_ws_reverse(self):
raise NotImplementedError raise NotImplementedError
async def _handle_http_api(self):
raise NotImplementedError

View File

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

View File

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

165
poetry.lock generated
View File

@ -60,7 +60,7 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" url = "https://mirrors.aliyun.com/pypi/simple"
[[package]] [[package]]
category = "dev" category = "main"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
optional = false optional = false
@ -73,7 +73,7 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" url = "https://mirrors.aliyun.com/pypi/simple"
[[package]] [[package]]
category = "dev" category = "main"
description = "Universal encoding detector for Python 2 and 3" description = "Universal encoding detector for Python 2 and 3"
name = "chardet" name = "chardet"
optional = false optional = false
@ -161,6 +161,49 @@ reference = "aliyun"
type = "legacy" type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" 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]] [[package]]
category = "dev" category = "dev"
description = "Turn HTML into equivalent Markdown-structured text." description = "Turn HTML into equivalent Markdown-structured text."
@ -174,6 +217,24 @@ reference = "aliyun"
type = "legacy" type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" 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]] [[package]]
category = "main" category = "main"
description = "A collection of framework independent HTTP protocol utils." description = "A collection of framework independent HTTP protocol utils."
@ -192,7 +253,42 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" url = "https://mirrors.aliyun.com/pypi/simple"
[[package]] [[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)" description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false optional = false
@ -383,6 +479,22 @@ reference = "aliyun"
type = "legacy" type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" 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]] [[package]]
category = "main" category = "main"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
@ -396,6 +508,19 @@ reference = "aliyun"
type = "legacy" type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple" 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]] [[package]]
category = "dev" category = "dev"
description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." 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"] scheduler = ["apscheduler"]
[metadata] [metadata]
content-hash = "5dc37a3a06ef422bb885c2f6b09179964e083f2005bf8873349431ebc4508152" content-hash = "e98c07c170a70d27c0cb15dd6b4cfd10e678a1df55bd3c3bebbbe26035b88a24"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.files] [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-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, {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 = [ html2text = [
{file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
{file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"}, {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 = [ httptools = [
{file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"}, {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"}, {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-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"},
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"}, {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 = [ idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, {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-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, {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 = [ six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, {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 = [ snowballstemmer = [
{file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
{file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},

View File

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

View File

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

View File

@ -10,4 +10,12 @@ test_matcher = on_message(Rule(), state={"default": 1})
@test_matcher.handle() @test_matcher.handle()
async def test_handler(bot, event: Event, state: dict): 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)