diff --git a/nonebot/adapters/satori/adapter.py b/nonebot/adapters/satori/adapter.py index 0d96887..18730a5 100644 --- a/nonebot/adapters/satori/adapter.py +++ b/nonebot/adapters/satori/adapter.py @@ -17,11 +17,21 @@ from .config import Config, ClientInfo from .exception import ApiNotAvailable from .models import Event as SatoriEvent -from .event import EVENT_CLASSES, Event, MessageEvent, LoginAddedEvent, InteractionEvent, LoginRemovedEvent +from .event import ( + EVENT_CLASSES, + Event, + MessageEvent, + LoginAddedEvent, + InteractionEvent, + LoginRemovedEvent, + LoginUpdatedEvent, +) from .models import ( Opcode, Payload, Identify, + LoginStatus, + MetaPayload, PayloadType, PingPayload, PongPayload, @@ -130,22 +140,19 @@ async def _authenticate(self, info: ClientInfo, ws: WebSocket) -> Optional[Liter ) return for login in resp.body.logins: - - if login.sn not in self.bots: - bot = Bot(self, login.sn, login, info, resp.body.proxy_urls) - self._bots[info.identity].add(bot.self_id) + if not login.user: + continue + if login.identity not in self.bots: + bot = Bot(self, login.user.id, login, info, resp.body.proxy_urls) + self._bots[info.identity].add(bot.identity) self.bot_connect(bot) - log( - "INFO", - f"Bot {login.user.id if login.user else login.sn} connected", - ) + log("INFO", f"Bot {bot.identity} connected") else: - self._bots[info.identity].add(login.sn) - bot = self.bots[login.sn] + bot = self.bots[login.identity] + self._bots[info.identity].add(bot.identity) bot._update(login) if not self.bots: log("WARNING", "No bots connected!") - return self.proxys[info.identity] = resp.body.proxy_urls return True @@ -232,33 +239,55 @@ async def _loop(self, info: ClientInfo, ws: WebSocket): e if payload.body["type"] != "internal" else None, ) else: - if not (bot := self.bots.get(event.login.sn)): - if isinstance(event, LoginAddedEvent): - bot = Bot(self, event.login.sn, event.login, info, self.proxys[info.identity]) - self._bots[info.identity].add(bot.self_id) - self.bot_connect(bot) - log( - "INFO", - f"Bot {event.login.user.id if event.login.user else event.login.sn} connected", - ) + if isinstance(event, LoginAddedEvent): + login = event.login + if not login.user: + log("WARNING", f"Received login-added event without user: {login}") + continue + bot = Bot(self, login.user.id, login, info, self.proxys[info.identity]) + self._bots[info.identity].add(bot.self_id) + self.bot_connect(bot) + log("INFO", f"Bot {bot.self_id} connected") + elif isinstance(event, LoginRemovedEvent): + login = event.login + if not login.user: + log("WARNING", f"Received login-removed event without user: {login}") + continue + bot = self.bots.get(login.identity) + if bot: + self.bot_disconnect(bot) + self._bots[info.identity].discard(bot.self_id) + log("INFO", f"Bot {bot.self_id} disconnected") else: log( "WARNING", - f"Received event for unknown bot " - f"{event.login.user.id if event.login.user else event.login.sn}", + f"Received login-removed event for unknown bot {login}", ) continue - if isinstance(event, LoginRemovedEvent): - self.bot_disconnect(self.bots[event.login.sn]) - self._bots[info.identity].discard(event.login.sn) - log( - "INFO", - f"Bot {event.login.user.id if event.login.user else event.login.sn} disconnected", - ) - continue + elif isinstance(event, LoginUpdatedEvent): + login = event.login + if not login.user: + log("WARNING", f"Received login-updated event without user: {login}") + continue + bot = self.bots.get(login.identity) + if bot: + bot._update(login) + else: + if login.status != LoginStatus.ONLINE: + log( + "WARNING", + f"Received login-updated event for unknown bot {login}", + ) + continue + bot = Bot(self, login.user.id, login, info, self.proxys[info.identity]) + self._bots[info.identity].add(bot.self_id) + self.bot_connect(bot) + log("INFO", f"Bot {bot.self_id} connected") else: - self.bots[event.login.sn]._update(event.login) - self._bots[info.identity].add(event.login.sn) + bot = self.bots.get(event.login.identity) + if not bot: + log("WARNING", f"Received event for unknown bot {event.login}") + continue if isinstance(event, (MessageEvent, InteractionEvent)): event = event.convert() @@ -268,6 +297,12 @@ async def _loop(self, info: ClientInfo, ws: WebSocket): elif isinstance(payload, PongPayload): log("TRACE", "Pong") continue + elif isinstance(payload, MetaPayload): + log("TRACE", f"Meta: {payload.body}") + self.proxys[info.identity] = payload.body.proxy_urls + for bot_id in self._bots[info.identity]: + bot = self.bots[bot_id] + bot.proxy_urls = payload.body.proxy_urls else: log( "WARNING", @@ -286,7 +321,7 @@ def payload_to_event(payload: SatoriEvent) -> Event: @override async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any: - log("DEBUG", f"Bot {bot.platform}:{bot.self_info.id} calling API {api}") + log("DEBUG", f"Bot {bot.identity} calling API {api}") api_handler: Optional[API] = getattr(bot.__class__, api, None) if api_handler is None: raise ApiNotAvailable(api) diff --git a/nonebot/adapters/satori/bot.py b/nonebot/adapters/satori/bot.py index 71fae88..06e1157 100644 --- a/nonebot/adapters/satori/bot.py +++ b/nonebot/adapters/satori/bot.py @@ -148,6 +148,7 @@ class Bot(BaseBot): @override def __init__(self, adapter: "Adapter", self_id: str, login: Login, info: ClientInfo, proxy_urls: list[str]): + self._self_id: str = self_id # Bot 配置信息 self.info: ClientInfo = info # Bot 自身所属平台 @@ -156,15 +157,19 @@ def __init__(self, adapter: "Adapter", self_id: str, login: Login, info: ClientI self._self_info: Login = login self.proxy_urls = proxy_urls - super().__init__(adapter, self_id) + super().__init__(adapter, self.identity) def __getattr__(self, item): raise AttributeError(f"'Bot' object has no attribute '{item}'") + @property + def identity(self): + return f"{self.platform}:{self.get_self_id()}" + def get_self_id(self): if self._self_info.user: return self._self_info.user.id - return self.self_id + return self._self_id @property def support_features(self): diff --git a/nonebot/adapters/satori/models.py b/nonebot/adapters/satori/models.py index 90bb4bf..77e2f9f 100644 --- a/nonebot/adapters/satori/models.py +++ b/nonebot/adapters/satori/models.py @@ -119,7 +119,7 @@ class LoginStatus(IntEnum): class Login(BaseModel): - sn: str + sn: int status: LoginStatus adapter: str platform: Optional[str] = None @@ -127,10 +127,10 @@ class Login(BaseModel): features: list[str] = Field(default_factory=list) @property - def id(self) -> str: + def identity(self): if not self.user: - raise ValueError(f"Login {self.sn} has not complete yet") - return self.user.id + raise ValueError(f"Login {self} has not complete yet") + return f"{self.platform or 'satori'}:{self.user.id}" if PYDANTIC_V2: model_config: ConfigDict = ConfigDict(extra="allow") # type: ignore @@ -145,7 +145,7 @@ def ensure_user(cls, values): if "self_id" in values and "user" not in values: values["user"] = {"id": values["self_id"]} if "sn" not in values: - values["sn"] = values["user"]["id"] + values["sn"] = 0 if "adapter" not in values: values["adapter"] = "satori" if "status" not in values: @@ -195,12 +195,12 @@ class Identify(BaseModel): class Ready(BaseModel): - logins: list[Login] + logins: list[LoginOnline] proxy_urls: list[str] = Field(default_factory=list) class Meta(BaseModel): - logins: list[Login] + logins: list[LoginOnline] proxy_urls: list[str] = Field(default_factory=list) diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 7f56571..dff2899 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -24,9 +24,9 @@ async def handle(bot: Bot): bot: Bot = ctx.create_bot( base=Bot, adapter=adapter, - self_id="0", + self_id="12345", login=Login( - sn="0", adapter="test", status=LoginStatus.ONLINE, platform="test", user=User(id="12345", name="test") + sn=0, adapter="test", status=LoginStatus.ONLINE, platform="test", user=User(id="12345", name="test") ), info=None, proxy_urls=[], @@ -41,7 +41,7 @@ async def handle(bot: Bot): "type": "message-created", "timestamp": 1000 * int(datetime.now().timestamp()), "login": { - "sn": "0", + "sn": 0, "adapter": "test", "platform": "test", "status": 1, diff --git a/tests/test_message.py b/tests/test_message.py index 347b2f7..0948412 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -1,5 +1,3 @@ -import pytest - from nonebot.adapters.satori.element import parse from nonebot.adapters.satori.message import Message, MessageSegment @@ -26,8 +24,7 @@ def test_message(): assert (Message() + "123").extract_plain_text() == "123" -@pytest.mark.asyncio() -async def test_message_rich_expr(): +def test_message_rich_expr(): raw = """\ Hello!