From 4ca43feae752aab9830771cf2abffe24ee8120ae Mon Sep 17 00:00:00 2001 From: nullqwertyuiop Date: Mon, 26 Aug 2024 21:26:12 +0800 Subject: [PATCH 1/4] bug(onebot11): no_cache when pulling Nick & Summary Signed-off-by: nullqwertyuiop --- avilla/onebot/v11/perform/action/scene.py | 58 +++++++++++++++++++---- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/avilla/onebot/v11/perform/action/scene.py b/avilla/onebot/v11/perform/action/scene.py index 4a244336..cd2ee427 100644 --- a/avilla/onebot/v11/perform/action/scene.py +++ b/avilla/onebot/v11/perform/action/scene.py @@ -1,8 +1,9 @@ from __future__ import annotations -from datetime import timedelta from typing import TYPE_CHECKING +from graia.amnesia.builtins.memcache import Memcache, MemcacheService + from avilla.core.ryanvk.collector.account import AccountCollector from avilla.core.selector import Selector from avilla.standard.core.profile import Avatar, Nick, Summary @@ -48,32 +49,73 @@ async def kick_member(self, target: Selector, reason: str | None = None, permane @m.pull("land.group.member", Nick) async def get_member_nick(self, target: Selector, route: ...) -> Nick: + cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache + if raw := await cache.get( + f"onebot11/account({self.account.route['account']}).group({target['group']}).member({target['member']})" + ): + return Nick(raw.get("card", "") or raw["nickname"], raw["nickname"], raw.get("title")) result = await self.account.connection.call( - "get_group_member_info", {"group_id": int(target["group"]), "user_id": int(target["member"])} + "get_group_member_info", + {"group_id": int(target["group"]), "user_id": int(target["member"]), "no_cache": True}, ) if result is None: raise RuntimeError(f"Failed to get member {target}") return Nick(result.get("card", "") or result["nickname"], result["nickname"], result.get("title")) @m.pull("land.friend", Nick) + async def get_friend_nick(self, target: Selector, route: ...) -> Nick: + cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache + if raw := await cache.get(f"onebot11/account({self.account.route['account']}).friend({target['friend']})"): + return Nick(raw["nickname"], raw.get("remark") or raw["nickname"], None) + if not (results := await self.account.connection.call("get_friend_list")): + raise RuntimeError(f"Failed to get friend {target}") + if not (result := next(filter(lambda x: x["user_id"] == int(target["friend"]), results), None)): + raise RuntimeError(f"Failed to get friend {target}") + return Nick(result["nickname"], result.get("remark") or result["nickname"], None) + @m.pull("land.stranger", Nick) - async def get_user_nick(self, target: Selector, route: ...) -> Nick: - result = await self.account.connection.call("get_stranger_info ", {"user_id": int(target.last_value)}) + async def get_stranger_nick(self, target: Selector, route: ...) -> Nick: + cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache + if raw := await cache.get(f"onebot11/account({self.account.route['account']}).stranger({target['stranger']})"): + return Nick(raw["nickname"], raw["nickname"], None) + result = await self.account.connection.call( + "get_stranger_info ", {"user_id": int(target["stranger"]), "no_cache": True} + ) if result is None: raise RuntimeError(f"Failed to get stranger {target}") return Nick(result["nickname"], result["nickname"], None) @m.pull("land.group", Summary) async def get_group_summary(self, target: Selector, route: ...) -> Summary: - result = await self.account.connection.call("get_group_info", {"group_id": int(target["group"])}) + cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache + if raw := await cache.get(f"onebot11/account({self.account.route['account']}).group({target['group']})"): + return Summary(raw["group_name"], None) + result = await self.account.connection.call( + "get_group_info", {"group_id": int(target["group"]), "no_cache": True} + ) if result is None: raise RuntimeError(f"Failed to get group {target}") return Summary(result["group_name"], None) @m.pull("land.friend", Summary) + async def get_friend_summary(self, target: Selector, route: ...) -> Summary: + cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache + if raw := await cache.get(f"onebot11/account({self.account.route['account']}).friend({target['friend']})"): + return Summary(raw["nickname"], "a friend contact assigned to this account") + if not (results := await self.account.connection.call("get_friend_list")): + raise RuntimeError(f"Failed to get friend {target}") + if not (result := next(filter(lambda x: x["user_id"] == int(target["friend"]), results), None)): + raise RuntimeError(f"Failed to get friend {target}") + return Summary(result["nickname"], "a friend contact assigned to this account") + @m.pull("land.stranger", Summary) - async def get_user_summary(self, target: Selector, route: ...) -> Summary: - result = await self.account.connection.call("get_stranger_info ", {"user_id": int(target.last_value)}) + async def get_stranger_summary(self, target: Selector, route: ...) -> Summary: + cache: Memcache = self.protocol.avilla.launch_manager.get_component(MemcacheService).cache + if raw := await cache.get(f"onebot11/account({self.account.route['account']}).stranger({target['stranger']})"): + return Summary(raw["nickname"], None) + result = await self.account.connection.call( + "get_stranger_info ", {"user_id": int(target["stranger"]), "no_cache": True} + ) if result is None: raise RuntimeError(f"Failed to get stranger {target}") return Summary(result["nickname"], None) @@ -86,4 +128,4 @@ async def get_user_avatar(self, target: Selector, route: ...) -> Avatar: @m.pull("land.group", Avatar) async def get_group_avatar(self, target: Selector, route: ...) -> Avatar: - return Avatar(f"https://p.qlogo.cn/gh/{target.pattern['group']}/{target.pattern['group']}/") + return Avatar(f"https://p.qlogo.cn/gh/{target['group']}/{target['group']}/") From 9fe6b40362ee1331b6420d3acd9d4be65d4c6bac Mon Sep 17 00:00:00 2001 From: nullqwertyuiop Date: Mon, 26 Aug 2024 21:37:45 +0800 Subject: [PATCH 2/4] feat(onebot11): impl caching when pulling Signed-off-by: nullqwertyuiop --- avilla/onebot/v11/perform/action/scene.py | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/avilla/onebot/v11/perform/action/scene.py b/avilla/onebot/v11/perform/action/scene.py index cd2ee427..dade5e9e 100644 --- a/avilla/onebot/v11/perform/action/scene.py +++ b/avilla/onebot/v11/perform/action/scene.py @@ -1,5 +1,7 @@ from __future__ import annotations +import asyncio +from datetime import timedelta from typing import TYPE_CHECKING from graia.amnesia.builtins.memcache import Memcache, MemcacheService @@ -60,6 +62,11 @@ async def get_member_nick(self, target: Selector, route: ...) -> Nick: ) if result is None: raise RuntimeError(f"Failed to get member {target}") + await cache.set( + f"onebot11/account({self.account.route['account']}).group({target['group']}).member({target['member']})", + result, + expire=timedelta(seconds=120), + ) return Nick(result.get("card", "") or result["nickname"], result["nickname"], result.get("title")) @m.pull("land.friend", Nick) @@ -69,6 +76,17 @@ async def get_friend_nick(self, target: Selector, route: ...) -> Nick: return Nick(raw["nickname"], raw.get("remark") or raw["nickname"], None) if not (results := await self.account.connection.call("get_friend_list")): raise RuntimeError(f"Failed to get friend {target}") + + await asyncio.gather( + *[ + cache.set( + f"onebot11/account({self.account.route['account']}).friend({x['user_id']})", + x, + expire=timedelta(seconds=120), + ) + for x in results + ] + ) if not (result := next(filter(lambda x: x["user_id"] == int(target["friend"]), results), None)): raise RuntimeError(f"Failed to get friend {target}") return Nick(result["nickname"], result.get("remark") or result["nickname"], None) @@ -83,6 +101,11 @@ async def get_stranger_nick(self, target: Selector, route: ...) -> Nick: ) if result is None: raise RuntimeError(f"Failed to get stranger {target}") + await cache.set( + f"onebot11/account({self.account.route['account']}).stranger({target['stranger']})", + result, + expire=timedelta(seconds=120), + ) return Nick(result["nickname"], result["nickname"], None) @m.pull("land.group", Summary) @@ -95,6 +118,11 @@ async def get_group_summary(self, target: Selector, route: ...) -> Summary: ) if result is None: raise RuntimeError(f"Failed to get group {target}") + await cache.set( + f"onebot11/account({self.account.route['account']}).group({target['group']})", + result, + expire=timedelta(seconds=120), + ) return Summary(result["group_name"], None) @m.pull("land.friend", Summary) @@ -106,6 +134,17 @@ async def get_friend_summary(self, target: Selector, route: ...) -> Summary: raise RuntimeError(f"Failed to get friend {target}") if not (result := next(filter(lambda x: x["user_id"] == int(target["friend"]), results), None)): raise RuntimeError(f"Failed to get friend {target}") + + await asyncio.gather( + *[ + cache.set( + f"onebot11/account({self.account.route['account']}).friend({x['user_id']})", + x, + expire=timedelta(seconds=120), + ) + for x in results + ] + ) return Summary(result["nickname"], "a friend contact assigned to this account") @m.pull("land.stranger", Summary) @@ -118,6 +157,11 @@ async def get_stranger_summary(self, target: Selector, route: ...) -> Summary: ) if result is None: raise RuntimeError(f"Failed to get stranger {target}") + await cache.set( + f"onebot11/account({self.account.route['account']}).stranger({target['stranger']})", + result, + expire=timedelta(seconds=120), + ) return Summary(result["nickname"], None) @m.pull("land.group.member", Avatar) From 9ce3d7782e599f3acdb533f386bd308a986b8c76 Mon Sep 17 00:00:00 2001 From: nullqwertyuiop Date: Tue, 27 Aug 2024 02:03:19 +0800 Subject: [PATCH 3/4] bug(onebot11): fix critical typo Signed-off-by: nullqwertyuiop --- avilla/onebot/v11/perform/action/scene.py | 4 ++-- test-onebot11.py | 27 +++++++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/avilla/onebot/v11/perform/action/scene.py b/avilla/onebot/v11/perform/action/scene.py index dade5e9e..7d0dec94 100644 --- a/avilla/onebot/v11/perform/action/scene.py +++ b/avilla/onebot/v11/perform/action/scene.py @@ -97,7 +97,7 @@ async def get_stranger_nick(self, target: Selector, route: ...) -> Nick: if raw := await cache.get(f"onebot11/account({self.account.route['account']}).stranger({target['stranger']})"): return Nick(raw["nickname"], raw["nickname"], None) result = await self.account.connection.call( - "get_stranger_info ", {"user_id": int(target["stranger"]), "no_cache": True} + "get_stranger_info", {"user_id": int(target["stranger"]), "no_cache": True} ) if result is None: raise RuntimeError(f"Failed to get stranger {target}") @@ -153,7 +153,7 @@ async def get_stranger_summary(self, target: Selector, route: ...) -> Summary: if raw := await cache.get(f"onebot11/account({self.account.route['account']}).stranger({target['stranger']})"): return Summary(raw["nickname"], None) result = await self.account.connection.call( - "get_stranger_info ", {"user_id": int(target["stranger"]), "no_cache": True} + "get_stranger_info", {"user_id": int(target["stranger"]), "no_cache": True} ) if result is None: raise RuntimeError(f"Failed to get stranger {target}") diff --git a/test-onebot11.py b/test-onebot11.py index 424dbddc..f1549f48 100644 --- a/test-onebot11.py +++ b/test-onebot11.py @@ -1,18 +1,31 @@ -from avilla.core import Avilla, Context, MessageReceived -from avilla.onebot.v11.protocol import OneBot11Protocol, OneBot11ReverseConfig +import os -from graia.amnesia.builtins.asgi import UvicornASGIService +from loguru import logger +from yarl import URL + +from avilla.core import Avilla, Context, MessageReceived, Selector +from avilla.onebot.v11.protocol import OneBot11ForwardConfig, OneBot11Protocol +from avilla.standard.core.profile import Nick, Summary avilla = Avilla() -config = OneBot11ReverseConfig(endpoint="ws", access_token="dfawdfafergergeaar") +# config = OneBot11ReverseConfig(endpoint="ws", access_token="dfawdfafergergeaar") +config = OneBot11ForwardConfig( + endpoint=URL(os.environ["ONEBOT11_ENDPOINT"]), access_token=os.environ["ONEBOT11_ACCESS_TOKEN"] +) avilla.apply_protocols(OneBot11Protocol().configure(config)) -avilla.launch_manager.add_component(UvicornASGIService("127.0.0.1", 9555)) +# avilla.launch_manager.add_component(UvicornASGIService("127.0.0.1", 9555)) @avilla.listen(MessageReceived) -async def on_message_received(ctx: Context, event: MessageReceived): - await ctx.scene.send_message("Hello, Avilla!") +async def on_message_received(ctx: Context): + nick: Nick = await ctx.pull(Nick, ctx.client) + summary: Summary = await ctx.pull(Summary, ctx.scene) + stranger_nick: Nick = await ctx.pull(Nick, Selector().land(ctx.land).stranger("2854196310")) # Q群管家 + logger.success(f"{nick = }") + logger.success(f"{summary = }") + logger.success(f"{stranger_nick = }") + avilla.launch() From 0d475bc807c7c20ba88efda7e498468f8c7d04dc Mon Sep 17 00:00:00 2001 From: nullqwertyuiop Date: Tue, 27 Aug 2024 02:56:47 +0800 Subject: [PATCH 4/4] feat(onebot11): cache metadata on receiving message Signed-off-by: nullqwertyuiop --- avilla/onebot/v11/perform/event/message.py | 34 ++++++++++++++++++++++ test-onebot11.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/avilla/onebot/v11/perform/event/message.py b/avilla/onebot/v11/perform/event/message.py index 862e177e..fe417c33 100644 --- a/avilla/onebot/v11/perform/event/message.py +++ b/avilla/onebot/v11/perform/event/message.py @@ -11,6 +11,7 @@ from avilla.onebot.v11.capability import OneBot11Capability from avilla.onebot.v11.collector.connection import ConnectionCollector from avilla.standard.core.message import MessageReceived, MessageRevoked, MessageSent +from avilla.standard.core.profile import Nick, Summary class OneBot11EventMessagePerform((m := ConnectionCollector())._): @@ -32,6 +33,14 @@ async def private_friend(self, raw_event: dict): friend, Selector().land(account.route["land"]).account(str(raw_event["self_id"])), ) + if nickname := raw_event["sender"].get("nickname"): + # See: https://github.com/botuniverse/onebot-11/blob/master/event/message.md?plain=1#L33 + # field "nickname" is not always present + context._collect_metadatas( + friend, + Nick(nickname, nickname, None), + Summary(nickname, "a friend contact assigned to this account"), + ) message = await OneBot11Capability(account.staff.ext({"context": context})).deserialize_chain( raw_event["message"] ) @@ -69,6 +78,14 @@ async def private_group(self, raw_event: dict): member, group.member(str(raw_event["self_id"])), ) + # "group" is not defined for event "message.private.group" in OneBot 11 standard, + # and I don't have implementations other than this, thus it's not cached + if nickname := raw_event["sender"].get("nickname"): + context._collect_metadatas( + member, + Nick(nickname, nickname, None), # "card" is not always present + Summary(nickname, None), + ) message = await OneBot11Capability(account.staff.ext({"context": context})).deserialize_chain( raw_event["message"] ) @@ -105,6 +122,12 @@ async def group(self, raw_event: dict): group, group.member(str(raw_event["self_id"])), ) + if nickname := raw_event["sender"].get("nickname"): + context._collect_metadatas( + member, + Nick(raw_event["sender"].get("card") or nickname, nickname, raw_event["sender"].get("title")), + Summary(nickname, None), + ) message = await OneBot11Capability(account.staff.ext({"context": context})).deserialize_chain( raw_event["message"] ) @@ -139,6 +162,12 @@ async def private_other(self, raw_event: dict): stranger, Selector().land(account.route["land"]).account(str(raw_event["self_id"])), ) + if nickname := raw_event["sender"].get("nickname"): + context._collect_metadatas( + stranger, + Nick(nickname, nickname, None), + Summary(nickname, None), + ) message = await OneBot11Capability(account.staff.ext({"context": context})).deserialize_chain( raw_event["message"] ) @@ -174,6 +203,11 @@ async def group_anonymous(self, raw_event: dict): group, group.member(str(raw_event["self_id"])), ) + context._collect_metadatas( + people, + Nick(raw_event["anonymous"]["name"], raw_event["anonymous"]["name"], None), + Summary(raw_event["anonymous"]["name"], None), + ) message = await OneBot11Capability(account.staff.ext({"context": context})).deserialize_chain( raw_event["message"] ) diff --git a/test-onebot11.py b/test-onebot11.py index f1549f48..8ba8815f 100644 --- a/test-onebot11.py +++ b/test-onebot11.py @@ -20,7 +20,7 @@ @avilla.listen(MessageReceived) async def on_message_received(ctx: Context): - nick: Nick = await ctx.pull(Nick, ctx.client) + nick: Nick = await ctx.pull(Nick, Selector(ctx.client.pattern)) summary: Summary = await ctx.pull(Summary, ctx.scene) stranger_nick: Nick = await ctx.pull(Nick, Selector().land(ctx.land).stranger("2854196310")) # Q群管家 logger.success(f"{nick = }")