Skip to content
This repository has been archived by the owner on Feb 20, 2024. It is now read-only.

Commit

Permalink
✨ 实验性支持 Websocket[beta]
Browse files Browse the repository at this point in the history
  • Loading branch information
CMHopeSunshine committed Nov 20, 2023
1 parent 47dbe2b commit 89ea4bb
Show file tree
Hide file tree
Showing 11 changed files with 946 additions and 233 deletions.
445 changes: 283 additions & 162 deletions nonebot/adapters/villa/adapter.py

Large diffs are not rendered by default.

43 changes: 27 additions & 16 deletions nonebot/adapters/villa/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
UploadImageParamsReturn,
Villa,
VillaRoomLink,
WebsocketInfo,
)
from .utils import API, get_img_extenion, get_img_md5, log

Expand Down Expand Up @@ -187,6 +188,8 @@ def __init__(
self.pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(bot_info.pub_key.encode())
self.verify_event = bot_info.verify_event
self._bot_info: Optional[Robot] = None
self._ws_info: Optional[WebsocketInfo] = None
self._ws_squence: int = 0

@override
def __repr__(self) -> str:
Expand Down Expand Up @@ -232,6 +235,16 @@ def current_villd_id(self) -> int:
raise ValueError(f"Bot {self.self_id} hasn't received any events yet.")
return self._bot_info.villa_id

@property
def ws_info(self) -> WebsocketInfo:
if self._ws_info is None:
raise RuntimeError(f"Bot {self.self_id} is not connected!")
return self._ws_info

@ws_info.setter
def ws_info(self, ws_info: WebsocketInfo):
self._ws_info = ws_info

async def handle_event(self, event: Event):
"""处理事件"""
if isinstance(event, SendMessageEvent):
Expand Down Expand Up @@ -425,15 +438,7 @@ async def parse_message_content(self, message: Message) -> MessageContentInfo:
def cal_len(x):
return len(x.encode("utf-16")) // 2 - 1

message = message.exclude(
"quote",
"image",
"post",
"badge",
"preview_link",
"components",
"panel",
)
message = message.exclude("quote", "image", "post", "badge", "preview_link")
message_text = ""
message_offset = 0
entities: List[TextEntity] = []
Expand Down Expand Up @@ -537,7 +542,7 @@ def cal_len(x):
mentioned = None

if not (message_text or entities):
if preview_link or badge or panel:
if preview_link or badge:
content = TextMessageContent(
text="\u200b",
preview_link=preview_link,
Expand Down Expand Up @@ -1074,11 +1079,21 @@ async def upload_image(
)
return parse_obj_as(ImageUploadResult, await self._request(request))

async def get_websocket_info(
self,
) -> WebsocketInfo:
request = Request(
method="GET",
url=self.adapter.base_url / "getWebsocketInfo",
headers=self.get_authorization_header(),
)
return parse_obj_as(WebsocketInfo, await self._request(request))


def _parse_components(components: List[Component]) -> Optional[Panel]:
small_total = [[]]
mid_total = [[]]
big_total = [[]]
big_total = []
for com in components:
com_lenght = len(com.text.encode("utf-8"))
if com_lenght <= 0:
Expand All @@ -1092,9 +1107,7 @@ def _parse_components(components: List[Component]) -> Optional[Panel]:
if len(mid_total[-1]) >= 2:
mid_total.append([])
elif com_lenght <= 30:
big_total[-1].append(com)
if len(big_total[-1]) >= 1:
big_total.append([])
big_total[-1].append([com])
else:
log("warning", f"component {com.id} text is too long, ignore")
if not small_total[-1]:
Expand All @@ -1103,8 +1116,6 @@ def _parse_components(components: List[Component]) -> Optional[Panel]:
if not mid_total[-1]:
mid_total.pop()
mid_total = mid_total or None
if not big_total[-1]:
big_total.pop()
big_total = big_total or None
if small_total or mid_total or big_total:
return Panel(
Expand Down
29 changes: 3 additions & 26 deletions nonebot/adapters/villa/config.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
from typing import List, Optional
from typing import List, Literal, Optional

from pydantic import BaseModel, Extra, Field


class BotInfo(BaseModel):
bot_id: str
bot_secret: str
connection_type: Literal["webhook", "websocket"] = "webhook"
test_villa_id: Optional[int] = None
pub_key: str
callback_url: Optional[str] = None
verify_event: bool = True
# ws_url: Optional[str] = None
# ws_secret: Optional[str] = None

# @validator("pub_key")
# @classmethod
# def format_pub_key(cls, v: str):
# v = v.strip()
# if v.startswith("-----BEGIN PUBLIC KEY-----"):
# v = v[26:]
# if v.endswith("-----END PUBLIC KEY-----"):
# v = v[:-24]
# v = v.replace(" ", "\n")
# if v[0] != "\n":
# v = "\n" + v
# if v[-1] != "\n":
# v += "\n"
# return "-----BEGIN PUBLIC KEY-----" + v + "-----END PUBLIC KEY-----\n"

# # 不能同时存在 callback_url 和 ws_url
# @root_validator
# @classmethod
# def check_url(cls, values):
# if values.get("callback_url") and values.get("ws_url"):
# raise ValueError("callback_url and ws_url cannot exist at the same time")
# return values


class Config(BaseModel, extra=Extra.ignore):
Expand Down
18 changes: 7 additions & 11 deletions nonebot/adapters/villa/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,8 @@ class JoinVillaEvent(NoticeEvent):
"""用户昵称"""
join_at: int
"""用户加入时间的时间戳"""

@property
def villa_id(self) -> int:
"""大别野ID"""
return self.robot.villa_id
villa_id: int
"""大别野 ID"""

@override
def get_event_description(self) -> str:
Expand Down Expand Up @@ -161,6 +158,8 @@ class SendMessageEvent(MessageEvent):
"""消息ID"""
bot_msg_id: Optional[str] = None
"""如果被回复的消息从属于机器人,则该字段不为空字符串"""
villa_id: int
"""大别野 ID"""
quote_msg: Optional[QuoteMessage] = None
"""回调消息引用消息的基础信息"""

Expand All @@ -181,11 +180,6 @@ def reply(self) -> Optional[QuoteMessage]:
"""消息的回复信息"""
return self.quote_msg

@property
def villa_id(self) -> int:
"""大别野ID"""
return self.robot.villa_id

@override
def get_event_description(self) -> str:
return escape_tag(
Expand Down Expand Up @@ -380,6 +374,8 @@ class AddQuickEmoticonEvent(NoticeEvent):
"""如果被回复的消息从属于机器人,则该字段不为空字符串"""
is_cancel: bool = False
"""是否是取消表情"""
emoticon_type: int
"""表情类型"""

@override
def get_user_id(self) -> str:
Expand Down Expand Up @@ -466,7 +462,7 @@ class ClickMsgComponentEvent(NoticeEvent):
"""如果被回复的消息从属于机器人,则该字段不为空字符串"""
component_id: str
"""机器人自定义的组件id"""
template_id: str
template_id: int
"""如果该组件模板为已创建模板,则template_id不为0"""
extra: str
"""机器人自定义透传信息"""
Expand Down
21 changes: 21 additions & 0 deletions nonebot/adapters/villa/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
)

if TYPE_CHECKING:
from betterproto import Message

from .api import ApiResponse


Expand All @@ -21,8 +23,27 @@ class NoLogException(BaseNoLogException, VillaAdapterException):
pass


class ReconnectError(VillaAdapterException):
def __init__(self, payload: Optional["Message"] = None):
super().__init__()
self.payload = payload

def __repr__(self) -> str:
if self.payload is None:
return "Receive unexpected data, reconnect."
return f"Reconnect because of {self.payload}"

def __str__(self) -> str:
return self.__repr__()


class DisconnectError(VillaAdapterException):
...


class ActionFailed(BaseActionFailed, VillaAdapterException):
def __init__(self, status_code: int, response: "ApiResponse"):
super().__init__()
self.status_code = status_code
self.response = response

Expand Down
4 changes: 2 additions & 2 deletions nonebot/adapters/villa/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,11 @@ def badge(
)

@staticmethod
def components(*components: Component) -> "ComponentsSegment":
def components(components: List[Component]) -> "ComponentsSegment":
return ComponentsSegment(
"components",
{
"components": list(components),
"components": components,
},
)

Expand Down
43 changes: 28 additions & 15 deletions nonebot/adapters/villa/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,19 @@ class BotAuth(BaseModel):

# http事件回调部分
# see https://webstatic.mihoyo.com/vila/bot/doc/callback.html
class CommandParam(BaseModel):
desc: str


class Command(BaseModel):
name: str
desc: Optional[str] = None
params: Optional[List[CommandParam]] = None


class TemplateCustomSettings(BaseModel):
name: str
url: str


class Template(BaseModel):
Expand All @@ -36,6 +46,8 @@ class Template(BaseModel):
desc: Optional[str] = None
icon: str
commands: Optional[List[Command]] = None
custom_settings: Optional[List[TemplateCustomSettings]] = None
is_allowed_add_to_other_villa: Optional[bool] = None


class Robot(BaseModel):
Expand Down Expand Up @@ -245,14 +257,10 @@ def extra_str_to_dict(cls, v: Any):
return v


class ComponentType(IntEnum):
Button = 1


class Component(BaseModel):
id: str
text: str
type: ComponentType = Field(default=ComponentType.Button, init=False)
type: int = 1
need_callback: Optional[bool] = None
extra: str = ""

Expand All @@ -275,9 +283,9 @@ class ButtonType(IntEnum):

class Button(Component):
c_type: ButtonType
input: Optional[str]
link: Optional[str]
need_token: Optional[bool]
input: Optional[str] = None
link: Optional[str] = None
need_token: Optional[bool] = None


class CallbackButton(Button):
Expand All @@ -286,23 +294,17 @@ class CallbackButton(Button):
init=False,
)
need_callback: Literal[True] = True
input: Optional[str] = Field(default=None, init=False)
link: Optional[str] = Field(default=None, init=False)
need_token: Optional[bool] = Field(default=None, init=False)


class InputButton(Button):
c_type: Literal[ButtonType.Input] = Field(default=ButtonType.Input, init=False)
input: str
link: Optional[str] = Field(default=None, init=False)
need_token: Optional[bool] = Field(default=None, init=False)


class LinkButton(Button):
c_type: Literal[ButtonType.Link] = Field(default=ButtonType.Link, init=False)
link: str
need_token: bool = False
input: Optional[str] = Field(default=None, init=False)
need_token: bool


class Trace(BaseModel):
Expand Down Expand Up @@ -541,6 +543,16 @@ class ImageUploadResult(BaseModel):
url: str


# Websocket 部分
# see https://webstatic.mihoyo.com/vila/bot/doc/websocket/websocket_api.html
class WebsocketInfo(BaseModel):
websocket_url: str
websocket_conn_uid: int
app_id: int
platform: int
device_id: str


for _, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and issubclass(obj, BaseModel):
obj.update_forward_refs()
Expand Down Expand Up @@ -607,4 +619,5 @@ class ImageUploadResult(BaseModel):
"ContentType",
"ImageUploadParams",
"UploadImageParamsReturn",
"WebsocketInfo",
]
Loading

0 comments on commit 89ea4bb

Please sign in to comment.