add http api

This commit is contained in:
yanyongyu 2020-08-01 22:03:40 +08:00
parent a39c2e223a
commit b3f82f3f22
6 changed files with 70 additions and 14 deletions

View File

@ -11,7 +11,12 @@ from nonebot.config import Config
class BaseBot(abc.ABC): class BaseBot(abc.ABC):
@abc.abstractmethod @abc.abstractmethod
def __init__(self, type: str, config: Config, *, websocket=None): def __init__(self,
type: str,
config: Config,
self_id: int,
*,
websocket=None):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod

View File

@ -10,6 +10,7 @@ from nonebot.event import Event
from nonebot.config import Config from nonebot.config import Config
from nonebot.message import handle_event from nonebot.message import handle_event
from nonebot.drivers import BaseWebSocket from nonebot.drivers import BaseWebSocket
from nonebot.exception import ApiNotAvailable
from nonebot.adapters import BaseBot, BaseMessage, BaseMessageSegment from nonebot.adapters import BaseBot, BaseMessage, BaseMessageSegment
@ -44,6 +45,7 @@ class Bot(BaseBot):
def __init__(self, def __init__(self,
connection_type: str, connection_type: str,
config: Config, config: Config,
self_id: int,
*, *,
websocket: BaseWebSocket = None): websocket: BaseWebSocket = None):
if connection_type not in ["http", "websocket"]: if connection_type not in ["http", "websocket"]:
@ -51,6 +53,7 @@ class Bot(BaseBot):
self.type = "coolq" self.type = "coolq"
self.connection_type = connection_type self.connection_type = connection_type
self.config = config self.config = config
self.self_id = self_id
self.websocket = websocket self.websocket = websocket
async def handle_message(self, message: dict): async def handle_message(self, message: dict):
@ -70,7 +73,24 @@ class Bot(BaseBot):
if self.type == "websocket": if self.type == "websocket":
pass pass
elif self.type == "http": elif self.type == "http":
pass api_root = self.config.api_root.get(self.self_id)
if not api_root:
raise ApiNotAvailable
elif not api_root.endswith("/"):
api_root += "/"
headers = {}
if self.config.access_token:
headers["Authorization"] = "Bearer " + self.config.access_token
async with httpx.AsyncClient() as client:
response = await client.post(api_root + api)
if 200 <= response.status_code < 300:
# TODO: handle http api response
return ...
raise httpx.HTTPError(
"<HttpFailed {0.status_code} for url: {0.url}>", response)
class MessageSegment(BaseMessageSegment): class MessageSegment(BaseMessageSegment):

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from typing import Set, Union
from ipaddress import IPv4Address from ipaddress import IPv4Address
from typing import Set, Dict, Union, Optional
from pydantic import BaseSettings from pydantic import BaseSettings
@ -15,14 +15,22 @@ class Env(BaseSettings):
class Config(BaseSettings): class Config(BaseSettings):
# nonebot configs
driver: str = "nonebot.drivers.fastapi" driver: str = "nonebot.drivers.fastapi"
host: IPv4Address = IPv4Address("127.0.0.1") host: IPv4Address = IPv4Address("127.0.0.1")
port: int = 8080 port: int = 8080
secret: Optional[str] = None
debug: bool = False debug: bool = False
# bot connection configs
api_root: Dict[int, str] = {}
access_token: Optional[str] = None
# bot runtime configs
superusers: Set[int] = set() superusers: Set[int] = set()
nickname: Union[str, Set[str]] = "" nickname: Union[str, Set[str]] = ""
# custom configs
custom_config: dict = {} custom_config: dict = {}
class Config: class Config:

View File

@ -67,7 +67,7 @@ class BaseWebSocket(object):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def close(self): async def close(self, code: int):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod

View File

@ -3,18 +3,19 @@
import json import json
import logging import logging
from typing import Optional from typing import Dict, Optional
from ipaddress import IPv4Address from ipaddress import IPv4Address
import uvicorn import uvicorn
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from starlette.websockets import WebSocketDisconnect from starlette.websockets import WebSocketDisconnect
from fastapi import Body, FastAPI, WebSocket as FastAPIWebSocket from fastapi import Body, status, Header, FastAPI, WebSocket as FastAPIWebSocket
from nonebot.log import logger from nonebot.log import logger
from nonebot.config import Config from nonebot.config import Config
from nonebot.drivers import BaseDriver, BaseWebSocket from nonebot.adapters import BaseBot
from nonebot.adapters.coolq import Bot as CoolQBot from nonebot.adapters.coolq import Bot as CoolQBot
from nonebot.drivers import BaseDriver, BaseWebSocket
class Driver(BaseDriver): class Driver(BaseDriver):
@ -28,6 +29,7 @@ class Driver(BaseDriver):
) )
self.config = config self.config = config
self._clients: Dict[int, BaseBot] = {}
self._server_app.post("/{adapter}/")(self._handle_http) self._server_app.post("/{adapter}/")(self._handle_http)
self._server_app.post("/{adapter}/http")(self._handle_http) self._server_app.post("/{adapter}/http")(self._handle_http)
@ -43,9 +45,13 @@ class Driver(BaseDriver):
return self._server_app return self._server_app
@property @property
def logger(self): def logger(self) -> logging.Logger:
return logging.getLogger("fastapi") return logging.getLogger("fastapi")
@property
def bots(self) -> Dict[int, BaseBot]:
return self._clients
def run(self, def run(self,
host: Optional[IPv4Address] = None, host: Optional[IPv4Address] = None,
port: Optional[int] = None, port: Optional[int] = None,
@ -102,12 +108,25 @@ class Driver(BaseDriver):
async def _handle_ws_reverse(self, async def _handle_ws_reverse(self,
adapter: str, adapter: str,
websocket: FastAPIWebSocket, websocket: FastAPIWebSocket,
self_id: int = Header(None),
access_token: str = OAuth2PasswordBearer( access_token: str = OAuth2PasswordBearer(
"/", auto_error=False)): "/", auto_error=False)):
websocket = WebSocket(websocket) websocket = WebSocket(websocket)
# TODO: Check authorization # TODO: Check authorization
# Create Bot Object
if adapter == "coolq":
bot = CoolQBot("websocket",
self.config,
self_id,
websocket=websocket)
else:
await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
return
await websocket.accept() await websocket.accept()
self._clients[self_id] = bot
while not websocket.closed: while not websocket.closed:
data = await websocket.receive() data = await websocket.receive()
@ -115,10 +134,9 @@ class Driver(BaseDriver):
if not data: if not data:
continue continue
logger.debug(f"Received message: {data}") await bot.handle_message(data)
if adapter == "coolq":
bot = CoolQBot("websocket", self.config, websocket=websocket) del self._clients[self_id]
await bot.handle_message(data)
class WebSocket(BaseWebSocket): class WebSocket(BaseWebSocket):
@ -135,8 +153,8 @@ class WebSocket(BaseWebSocket):
await self.websocket.accept() await self.websocket.accept()
self._closed = False self._closed = False
async def close(self): async def close(self, code: int = status.WS_1000_NORMAL_CLOSURE):
await self.websocket.close() await self.websocket.close(code=code)
self._closed = True self._closed = True
async def receive(self) -> Optional[dict]: async def receive(self) -> Optional[dict]:

View File

@ -28,3 +28,8 @@ class RejectedException(Exception):
class FinishedException(Exception): class FinishedException(Exception):
"""Finish handling a message""" """Finish handling a message"""
pass pass
class ApiNotAvailable(Exception):
"""Api is not available"""
pass