From 623697618e17bf1a2fec679e7ec44531bb71e93c Mon Sep 17 00:00:00 2001 From: jigsaw Date: Fri, 9 Aug 2024 00:55:03 +0800 Subject: [PATCH] :construction: middleware(discord): basic message --- nonebot_plugin_all4one/middlewares/discord.py | 145 ++++++++++++++++++ pdm.lock | 46 ++++-- pyproject.toml | 7 +- 3 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 nonebot_plugin_all4one/middlewares/discord.py diff --git a/nonebot_plugin_all4one/middlewares/discord.py b/nonebot_plugin_all4one/middlewares/discord.py new file mode 100644 index 0000000..33f3a60 --- /dev/null +++ b/nonebot_plugin_all4one/middlewares/discord.py @@ -0,0 +1,145 @@ +from typing import Any, Union, Literal, Optional + +from anyio import open_file +from httpx import AsyncClient +from pydantic import TypeAdapter +from nonebot.adapters.onebot.v12 import Event as OneBotEvent +from nonebot.adapters.onebot.v12 import Adapter as OneBotAdapter +from nonebot.adapters.onebot.v12 import Message as OneBotMessage +from nonebot.adapters.onebot.v12 import MessageSegment as OneBotMessageSegment +from nonebot.adapters.discord import ( + Bot, + Event, + Adapter, + Message, + MessageEvent, + MessageSegment, + GuildMessageCreateEvent, + DirectMessageCreateEvent, +) + +from .base import supported_action +from ..database import get_file, upload_file +from .base import Middleware as BaseMiddleware + + +class Middleware(BaseMiddleware): + bot: Bot + + @staticmethod + def get_name(): + return Adapter.get_name() + + def get_platform(self): + return "discord" + + async def to_onebot_event(self, event: Event) -> list[OneBotEvent]: + event_dict = {} + if (type := event.get_type()) not in ["message", "notice", "request"]: + return [] + event_dict["type"] = type + event_dict["self"] = self.get_bot_self().model_dump() + if isinstance(event, MessageEvent): + event_dict["id"] = str(event.id) + event_dict["time"] = event.timestamp + event_dict["sub_type"] = "" + event_dict["message_id"] = str(event.message_id) + event_dict["message"] = await self.to_onebot_message(event) + event_dict["alt_message"] = str(event.original_message) + event_dict["user_id"] = event.get_user_id() + if isinstance(event, GuildMessageCreateEvent): + event_dict["detail_type"] = "channel" + event_dict["guild_id"] = str(event.guild_id) + event_dict["channel_id"] = str(event.channel_id) + elif isinstance(event, DirectMessageCreateEvent): + event_dict["detail_type"] = "private" + if event.reply: + event_dict["message"].insert( + 0, + OneBotMessageSegment.reply( + str(event.reply.id), user_id=str(event.reply.author.id) + ), + ) + + if event_out := OneBotAdapter.json_to_event( + event_dict, "nonebot-plugin-all4one" + ): + return [event_out] + return [] + + async def to_onebot_message(self, event: MessageEvent) -> OneBotMessage: + message_list = [] + for segment in event.original_message: + if segment.type == "text": + message_list.append(OneBotMessageSegment.text(segment.data["text"])) + elif segment.type == "mention_user": + message_list.append( + OneBotMessageSegment.mention(segment.data["user_id"]) + ) + for attachment in event.attachments: + async with AsyncClient() as client: + try: + data = (await client.get(attachment.url)).content + except Exception: + data = None + file_id = await upload_file( + attachment.filename, self.get_name(), attachment.id, data=data + ) + if attachment.content_type.startswith("image"): + message_list.append(OneBotMessageSegment.image(file_id)) + else: + message_list.append(OneBotMessageSegment.file(file_id)) + if event.original_message.count("mention_everyone"): + message_list.append(OneBotMessageSegment.mention_all()) + + return OneBotMessage(message_list) + + @supported_action + async def send_message( + self, + *, + detail_type: Union[Literal["private", "group", "channel"], str], + user_id: Optional[str] = None, + group_id: Optional[str] = None, + guild_id: Optional[str] = None, + channel_id: Optional[str] = None, + message: OneBotMessage, + **kwargs: Any, + ) -> dict[Union[Literal["message_id", "time"], str], Any]: + if detail_type == "group": + chat_id = group_id + elif detail_type == "private" and user_id is not None: + chat_id = (await self.bot.create_DM(recipient_id=int(user_id))).id + else: + chat_id = channel_id + chat_id = str(chat_id) + + message_list = [] + message = TypeAdapter(OneBotMessage).validate_python(message) + for segment in message: + if segment.type == "text": + message_list.append(MessageSegment.text(segment.data["text"])) + elif segment.type == "mention": + message_list.append( + MessageSegment.mention_user(int(segment.data["user_id"])) + ) + elif segment.type == "mention_all": + message_list.append(MessageSegment.mention_everyone()) + elif segment.type == "reply": + message_list.append( + MessageSegment.reference(int(segment.data["message_id"])) + ) + elif segment.type in ("image", "file"): + file = await get_file(segment.data["file_id"], self.get_name()) + if file.path: + async with await open_file(file.path, "rb") as f: + data = await f.read() + message_list.append( + MessageSegment.attachment(file.name, content=data) + ) + discord_message = Message(message_list) + result = await self.bot.send_to(int(chat_id), discord_message) + return { + "message_id": str(result.id), + "time": result.timestamp, + } diff --git a/pdm.lock b/pdm.lock index 93da0fd..7b2ed9a 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "tests", "adapters", "qq", "telegram", "nonebot", "impl", "pre-commit"] +groups = ["default", "tests", "adapters", "qq", "telegram", "nonebot", "impl", "pre-commit", "discord"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:a2d510decf6838aaa1e53f00f07b0ed8e3c534e634ac4ab1d3f84f329dbebc94" +content_hash = "sha256:5df0d166e58191845554efb4a18a511ed5bee16d9474230b77fcafba2c989d61" [[package]] name = "aiosqlite" @@ -42,7 +42,7 @@ name = "annotated-types" version = "0.6.0" requires_python = ">=3.8" summary = "Reusable constraint types to use with typing.Annotated" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] files = [ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, @@ -255,7 +255,7 @@ name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." -groups = ["adapters", "default", "impl", "nonebot", "pre-commit", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "pre-commit", "qq", "telegram", "tests"] marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, @@ -570,7 +570,7 @@ name = "idna" version = "3.6" requires_python = ">=3.5" summary = "Internationalized Domain Names in Applications (IDNA)" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] files = [ {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, @@ -633,7 +633,7 @@ name = "loguru" version = "0.7.2" requires_python = ">=3.5" summary = "Python logging made (stupidly) simple" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] dependencies = [ "colorama>=0.3.4; sys_platform == \"win32\"", "win32-setctime>=1.0.0; sys_platform == \"win32\"", @@ -777,7 +777,7 @@ name = "multidict" version = "6.0.5" requires_python = ">=3.7" summary = "multidict implementation" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, @@ -883,6 +883,20 @@ files = [ {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, ] +[[package]] +name = "nonebot-adapter-discord" +version = "0.1.8" +requires_python = "<4.0,>=3.9" +summary = "Discord adapter for nonebot2" +groups = ["adapters", "discord"] +dependencies = [ + "nonebot2<3.0.0,>=2.2.1", +] +files = [ + {file = "nonebot_adapter_discord-0.1.8-py3-none-any.whl", hash = "sha256:d063bf524f6a75c5c123f2d04227e0ec62c2433f56b28fb92fa5eb2aebef1c16"}, + {file = "nonebot_adapter_discord-0.1.8.tar.gz", hash = "sha256:5d3a7a8e0ab23b7ae84551b479c40c5d09733b15d09538d64765c5af54721781"}, +] + [[package]] name = "nonebot-adapter-onebot" version = "2.4.4" @@ -1007,7 +1021,7 @@ name = "nonebot2" version = "2.3.2" requires_python = "<4.0,>=3.9" summary = "An asynchronous python bot framework." -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] dependencies = [ "loguru<1.0.0,>=0.6.0", "pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.10.0", @@ -1149,7 +1163,7 @@ name = "pydantic" version = "2.8.2" requires_python = ">=3.8" summary = "Data validation using Python type hints" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] dependencies = [ "annotated-types>=0.4.0", "pydantic-core==2.20.1", @@ -1166,7 +1180,7 @@ name = "pydantic-core" version = "2.20.1" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] @@ -1254,7 +1268,7 @@ files = [ name = "pygtrie" version = "2.5.0" summary = "A pure Python trie data structure implementation." -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] files = [ {file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"}, {file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"}, @@ -1356,7 +1370,7 @@ name = "python-dotenv" version = "1.0.1" requires_python = ">=3.8" summary = "Read key-value pairs from a .env file and set them as environment variables" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1635,7 +1649,7 @@ name = "tomli" version = "2.0.1" requires_python = ">=3.7" summary = "A lil' TOML parser" -groups = ["adapters", "default", "impl", "nonebot", "pre-commit", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "pre-commit", "qq", "telegram", "tests"] marker = "python_version < \"3.11\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, @@ -1647,7 +1661,7 @@ name = "typing-extensions" version = "4.12.2" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" -groups = ["adapters", "default", "impl", "nonebot", "pre-commit", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "pre-commit", "qq", "telegram", "tests"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1781,7 +1795,7 @@ name = "win32-setctime" version = "1.1.0" requires_python = ">=3.5" summary = "A small Python utility to set file creation time on Windows" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] marker = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, @@ -1793,7 +1807,7 @@ name = "yarl" version = "1.9.4" requires_python = ">=3.7" summary = "Yet another URL library" -groups = ["adapters", "default", "impl", "nonebot", "qq", "telegram", "tests"] +groups = ["adapters", "default", "discord", "impl", "nonebot", "qq", "telegram", "tests"] dependencies = [ "idna>=2.0", "multidict>=4.0", diff --git a/pyproject.toml b/pyproject.toml index 3cb3e1e..32de25e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,12 @@ license = { text = "AGPL-3.0-only" } [project.optional-dependencies] telegram = ["nonebot-adapter-telegram>=0.1.0b13"] qq = ["nonebot-adapter-qq>=0.2.2"] -adapters = ["nonebot-adapter-telegram>=0.1.0b13", "nonebot-adapter-qq>=0.2.4"] +discord = ["nonebot-adapter-discord>=0.1.8"] +adapters = [ + "nonebot-adapter-telegram>=0.1.0b13", + "nonebot-adapter-qq>=0.2.4", + "nonebot-adapter-discord>=0.1.8", +] [build-system] requires = ["pdm-backend"]