From 1e9b5b6c150f5d33e4d9715a26d49f2b9c993be3 Mon Sep 17 00:00:00 2001 From: meetwq Date: Tue, 27 Feb 2024 18:40:55 +0800 Subject: [PATCH 1/3] feat: support adapter qq --- README.md | 1 + nonebot_plugin_userinfo/__init__.py | 1 + nonebot_plugin_userinfo/adapters/__init__.py | 1 + nonebot_plugin_userinfo/adapters/qq.py | 68 ++++++++++++ nonebot_plugin_userinfo/image_source.py | 9 ++ poetry.lock | 77 +++++++------ pyproject.toml | 3 +- tests/test_qq.py | 107 +++++++++++++++++++ 8 files changed, 232 insertions(+), 35 deletions(-) create mode 100644 nonebot_plugin_userinfo/adapters/qq.py create mode 100644 tests/test_qq.py diff --git a/README.md b/README.md index 3a26a14..9ef5375 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ async def handle(user_info: UserInfo = BotUserInfo()): # 获取Bot用户信息 - [x] Discord - [x] DoDo - [x] Satori +- [x] QQ ### 鸣谢 diff --git a/nonebot_plugin_userinfo/__init__.py b/nonebot_plugin_userinfo/__init__.py index 06dc5c9..f8cf0a7 100644 --- a/nonebot_plugin_userinfo/__init__.py +++ b/nonebot_plugin_userinfo/__init__.py @@ -25,5 +25,6 @@ "~discord", "~dodo", "~satori", + "~qq", }, ) diff --git a/nonebot_plugin_userinfo/adapters/__init__.py b/nonebot_plugin_userinfo/adapters/__init__.py index f964ca5..cbafa4e 100644 --- a/nonebot_plugin_userinfo/adapters/__init__.py +++ b/nonebot_plugin_userinfo/adapters/__init__.py @@ -5,6 +5,7 @@ from . import kaiheila as kaiheila from . import onebot_v11 as onebot_v11 from . import onebot_v12 as onebot_v12 +from . import qq as qq from . import red as red from . import satori as satori from . import telegram as telegram diff --git a/nonebot_plugin_userinfo/adapters/qq.py b/nonebot_plugin_userinfo/adapters/qq.py new file mode 100644 index 0000000..5762daa --- /dev/null +++ b/nonebot_plugin_userinfo/adapters/qq.py @@ -0,0 +1,68 @@ +import uuid +from typing import Optional + +from nonebot.exception import ActionFailed +from nonebot.log import logger + +from ..getter import UserInfoGetter, register_user_info_getter +from ..image_source import ImageUrl, QQAvatarOpenId +from ..user_info import UserInfo + +try: + from nonebot.adapters.qq import ( + Bot, + C2CMessageCreateEvent, + Event, + GroupAtMessageCreateEvent, + GuildMessageEvent, + ) + + @register_user_info_getter(Bot, Event) + class Getter(UserInfoGetter[Bot, Event]): + async def _get_info(self, user_id: str) -> Optional[UserInfo]: + user = None + + if self.bot.self_id == user_id: + try: + user = await self.bot.me() + except ActionFailed as e: + logger.warning(f"Error calling me: {e}") + + if not user and isinstance(self.event, GuildMessageEvent): + if self.event.author.id == user_id: + user = self.event.author + else: + guild_id = self.event.guild_id + try: + member = await self.bot.get_member( + guild_id=guild_id, user_id=user_id + ) + if member: + user = member.user + except ActionFailed as e: + logger.warning(f"Error calling get_member: {e}") + + if user: + return UserInfo( + user_id=user.id, + user_name=user.username or "", + user_avatar=ImageUrl(url=user.avatar) if user.avatar else None, + ) + + if isinstance( + self.event, (C2CMessageCreateEvent, GroupAtMessageCreateEvent) + ): + try: + uuid.UUID(user_id) + return UserInfo( + user_id=user_id, + user_name="", + user_avatar=QQAvatarOpenId( + appid=self.bot.bot_info.id, user_openid=user_id + ), + ) + except ValueError: + pass + +except ImportError: + pass diff --git a/nonebot_plugin_userinfo/image_source.py b/nonebot_plugin_userinfo/image_source.py index a00e971..7f638f8 100644 --- a/nonebot_plugin_userinfo/image_source.py +++ b/nonebot_plugin_userinfo/image_source.py @@ -64,6 +64,15 @@ async def get_image(self) -> bytes: return data +class QQAvatarOpenId(ImageSource): + appid: str + user_openid: str + + async def get_image(self) -> bytes: + url = f"https://q.qlogo.cn/qqapp/{self.appid}/{self.user_openid}/100" + return await download_url(url) + + class TelegramFile(ImageSource): token: str file_path: str diff --git a/poetry.lock b/poetry.lock index cbb1082..be40a74 100644 --- a/poetry.lock +++ b/poetry.lock @@ -69,13 +69,13 @@ requests = ">=2.21,<3.0" [[package]] name = "cachetools" -version = "5.3.2" +version = "5.3.3" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] [[package]] @@ -771,21 +771,17 @@ typing-extensions = ">=4.7.1" [[package]] name = "nonebot-adapter-discord" -version = "0.1.3" +version = "0.1.4" description = "Discord adapter for nonebot2" optional = false -python-versions = "^3.8" -files = [] -develop = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "nonebot_adapter_discord-0.1.4-py3-none-any.whl", hash = "sha256:e65f30ec52d826cce31dc481389ca72a81b87338b749b89dde247f81adbd8724"}, + {file = "nonebot_adapter_discord-0.1.4.tar.gz", hash = "sha256:686024c6449fab1869c554eef96318259d4a5b161a1623e8681968df72cb2b4d"}, +] [package.dependencies] -nonebot2 = {git = "https://github.com/nonebot/nonebot2.git"} - -[package.source] -type = "git" -url = "https://github.com/nonebot/adapter-discord.git" -reference = "HEAD" -resolved_reference = "16ff10098cf4f57cb9a2b4ca6ee7b9471625f86c" +nonebot2 = ">=2.2.1,<3.0.0" [[package]] name = "nonebot-adapter-dodo" @@ -849,6 +845,23 @@ nonebot2 = ">=2.2.0,<3.0.0" pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" typing-extensions = ">=4.0.0,<5.0.0" +[[package]] +name = "nonebot-adapter-qq" +version = "1.4.1" +description = "QQ adapter for nonebot2" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "nonebot_adapter_qq-1.4.1-py3-none-any.whl", hash = "sha256:dbe59b9559943cb7940f0ff9190ec7428db75be9bc4ba1dbe2cba7ff6e249c23"}, + {file = "nonebot_adapter_qq-1.4.1.tar.gz", hash = "sha256:45e99d8caaf7e52857cbd2661d07fd1451bf56d63ef49e80b833933a222dcb26"}, +] + +[package.dependencies] +nonebot2 = ">=2.2.0,<3.0.0" +pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" +typing-extensions = ">=4.4.0,<5.0.0" +yarl = ">=1.9.0,<2.0.0" + [[package]] name = "nonebot-adapter-red" version = "0.9.0" @@ -906,20 +919,22 @@ name = "nonebot2" version = "2.2.1" description = "An asynchronous python bot framework." optional = false -python-versions = "^3.8" -files = [] -develop = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "nonebot2-2.2.1-py3-none-any.whl", hash = "sha256:88f2bb456bf90922925bbe489a9effe3b09300f3aa50bfa75ee50d8a83d7330f"}, + {file = "nonebot2-2.2.1.tar.gz", hash = "sha256:fe57692300571b00724999238545d8d894523460e6835a11b326a2e1cdf98fc4"}, +] [package.dependencies] fastapi = {version = ">=0.93.0,<1.0.0", optional = true, markers = "extra == \"fastapi\" or extra == \"all\""} loguru = ">=0.6.0,<1.0.0" -pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1" -pygtrie = "^2.4.1" +pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" +pygtrie = ">=2.4.1,<3.0.0" python-dotenv = ">=0.21.0,<2.0.0" -tomli = {version = "^2.0.1", markers = "python_version < \"3.11\""} +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} typing-extensions = ">=4.4.0,<5.0.0" uvicorn = {version = ">=0.20.0,<1.0.0", extras = ["standard"], optional = true, markers = "extra == \"quart\" or extra == \"fastapi\" or extra == \"all\""} -yarl = "^1.7.2" +yarl = ">=1.7.2,<2.0.0" [package.extras] aiohttp = ["aiohttp[speedups] (>=3.9.0b0,<4.0.0)"] @@ -929,12 +944,6 @@ httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"] quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] websockets = ["websockets (>=10.0)"] -[package.source] -type = "git" -url = "https://github.com/nonebot/nonebot2.git" -reference = "HEAD" -resolved_reference = "d1601bf2fe351626cd94e8e89d9ce734abf0e285" - [[package]] name = "nonebug" version = "0.3.5" @@ -1344,13 +1353,13 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -1417,13 +1426,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -1830,4 +1839,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "c31a1cb308a8295a5261e7d1c31abca553ee454c720485fec45c405b67ee3285" +content-hash = "b8a91a8aa5035bf1a2ac6281256bcea0c5aa4d1c5cf5fd8f3067436bdfa4499b" diff --git a/pyproject.toml b/pyproject.toml index fbb0b95..d7dd900 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,10 @@ nonebot-adapter-kaiheila = "^0.3.1" nonebot-adapter-telegram = { git = "https://github.com/nonebot/adapter-telegram.git" } nonebot-adapter-feishu = "^2.4.0" nonebot-adapter-red = "^0.9.0" -nonebot-adapter-discord = { git = "https://github.com/nonebot/adapter-discord.git" } +nonebot-adapter-discord = "^0.1.4" nonebot-adapter-dodo = "^0.2.0" nonebot-adapter-satori = "^0.9.3" +nonebot-adapter-qq = "^1.4.1" [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/tests/test_qq.py b/tests/test_qq.py new file mode 100644 index 0000000..2b98634 --- /dev/null +++ b/tests/test_qq.py @@ -0,0 +1,107 @@ +from datetime import datetime + +from nonebot.adapters.qq import ( + Bot, + EventType, + GroupAtMessageCreateEvent, + MessageCreateEvent, +) +from nonebot.adapters.qq.config import BotInfo +from nonebot.adapters.qq.models import GroupMemberAuthor, Member, User +from nonebug import App + + +def _fake_message_create_event(msg: str) -> MessageCreateEvent: + return MessageCreateEvent( + id="id", + __type__=EventType.MESSAGE_CREATE, + channel_id="6677", + guild_id="5566", + author=User(id="3344", username="MyUser", avatar="http://xxx.jpg"), + content=msg, + ) + + +def _fake_group_at_message_create_event(msg: str) -> GroupAtMessageCreateEvent: + return GroupAtMessageCreateEvent( + id="id", + content=msg, + timestamp="1234567890", + __type__=EventType.GROUP_AT_MESSAGE_CREATE, + author=GroupMemberAuthor( + id="id", + member_openid="3F21411B784D403E811E68BF3E2944D8", + ), + group_openid="19303D3617EF432999F24342F99AEC65", + ) + + +async def test_message_event(app: App): + from nonebot_plugin_userinfo import UserInfo + from nonebot_plugin_userinfo.image_source import ImageUrl, QQAvatarOpenId + + async with app.test_matcher() as ctx: + bot = ctx.create_bot( + base=Bot, self_id="2233", bot_info=BotInfo(id="2233", token="", secret="") + ) + + user_info = UserInfo( + user_id="3344", + user_name="MyUser", + user_displayname=None, + user_remark=None, + user_avatar=ImageUrl(url="http://xxx.jpg"), + user_gender="unknown", + ) + event = _fake_message_create_event("/user_info") + ctx.receive_event(bot, event) + ctx.should_call_send(event, "", True, user_info=user_info) + + user_info = UserInfo( + user_id="1234", + user_name="member", + user_displayname=None, + user_remark=None, + user_avatar=ImageUrl(url="http://xxx.jpg"), + user_gender="unknown", + ) + event = _fake_message_create_event("/user_info 1234") + ctx.receive_event(bot, event) + ctx.should_call_api( + "get_member", + {"guild_id": "5566", "user_id": "1234"}, + Member( + user=User(id="1234", username="member", avatar="http://xxx.jpg"), + joined_at=datetime.now(), + ), + ) + ctx.should_call_send(event, "", True, user_info=user_info) + + user_info = UserInfo( + user_id="2233", + user_name="Bot", + user_displayname=None, + user_remark=None, + user_avatar=ImageUrl(url="http://xxx.jpg"), + user_gender="unknown", + ) + event = _fake_message_create_event("/bot_user_info") + ctx.receive_event(bot, event) + ctx.should_call_api( + "me", {}, User(id="2233", username="Bot", avatar="http://xxx.jpg") + ) + ctx.should_call_send(event, "", True, user_info=user_info) + + user_info = UserInfo( + user_id="3F21411B784D403E811E68BF3E2944D8", + user_name="", + user_displayname=None, + user_remark=None, + user_avatar=QQAvatarOpenId( + appid="2233", user_openid="3F21411B784D403E811E68BF3E2944D8" + ), + user_gender="unknown", + ) + event = _fake_group_at_message_create_event("/user_info") + ctx.receive_event(bot, event) + ctx.should_call_send(event, "", True, user_info=user_info) From c8793d09f747d30885c1035c7c1e48a53772b561 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:42:17 +0000 Subject: [PATCH 2/3] style: auto fix by pre-commit hooks --- tests/plugins/echo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/plugins/echo.py b/tests/plugins/echo.py index fac5b78..08c8148 100644 --- a/tests/plugins/echo.py +++ b/tests/plugins/echo.py @@ -22,5 +22,7 @@ async def _(bot: Bot, event: Event, arg: Message = CommandArg()): @bot_user_info_cmd.handle() -async def _(user_info: Optional[UserInfo] = BotUserInfo(use_cache=False)): # 获取Bot用户信息 +async def _( + user_info: Optional[UserInfo] = BotUserInfo(use_cache=False), +): # 获取Bot用户信息 await bot_user_info_cmd.send("", user_info=user_info) From 421fecbc5ca5a2f221ca2d113f788dfcb3683a7d Mon Sep 17 00:00:00 2001 From: meetwq Date: Wed, 28 Feb 2024 23:09:35 +0800 Subject: [PATCH 3/3] remove openid uuid check --- nonebot_plugin_userinfo/adapters/qq.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/nonebot_plugin_userinfo/adapters/qq.py b/nonebot_plugin_userinfo/adapters/qq.py index 5762daa..7bdd743 100644 --- a/nonebot_plugin_userinfo/adapters/qq.py +++ b/nonebot_plugin_userinfo/adapters/qq.py @@ -1,4 +1,3 @@ -import uuid from typing import Optional from nonebot.exception import ActionFailed @@ -52,17 +51,13 @@ async def _get_info(self, user_id: str) -> Optional[UserInfo]: if isinstance( self.event, (C2CMessageCreateEvent, GroupAtMessageCreateEvent) ): - try: - uuid.UUID(user_id) - return UserInfo( - user_id=user_id, - user_name="", - user_avatar=QQAvatarOpenId( - appid=self.bot.bot_info.id, user_openid=user_id - ), - ) - except ValueError: - pass + return UserInfo( + user_id=user_id, + user_name="", + user_avatar=QQAvatarOpenId( + appid=self.bot.bot_info.id, user_openid=user_id + ), + ) except ImportError: pass