Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

further support for qq adapter #117

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nonebot_plugin_saa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
from .registries import SaaTarget as SaaTarget
from .registries import get_target as get_target
from .registries import TargetQQGroup as TargetQQGroup
from .registries import TargetQQGroupOpenId as TargetQQGroupOpenId
from .registries import PlatformTarget as PlatformTarget
from .registries import extract_target as extract_target
from .utils import SupportedAdapters as SupportedAdapters
from .registries import TargetQQPrivate as TargetQQPrivate
from .registries import TargetQQPrivateOpenId as TargetQQPrivateOpenId
from .registries import TargetOB12Unknow as TargetOB12Unknow
from .registries import TargetDoDoChannel as TargetDoDoChannel
from .registries import TargetDoDoPrivate as TargetDoDoPrivate
Expand Down Expand Up @@ -43,5 +45,6 @@
"~telegram",
"~feishu",
"~red",
"~qq",
},
)
1 change: 1 addition & 0 deletions nonebot_plugin_saa/adapters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from . import qq as qq
from . import red as red
from . import dodo as dodo
from . import feishu as feishu
Expand Down
261 changes: 261 additions & 0 deletions nonebot_plugin_saa/adapters/qq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
from functools import partial
from typing import List, Union, Literal, Optional
from pydantic import BaseModel
from datetime import datetime

from nonebot.adapters import Event
from nonebot.adapters import Bot as BaseBot

from ..utils import SupportedAdapters
from ..types import Text, Image, Reply, Mention
from ..auto_select_bot import register_list_targets
from ..abstract_factories import (
MessageFactory,
MessageSegmentFactory,
register_ms_adapter,
assamble_message_factory,
)
from ..registries import (
Receipt,
MessageId,
TargetQQGroupOpenId,
PlatformTarget,
TargetQQPrivateOpenId,
QQGuildDMSManager,
TargetQQGuildDirect,
TargetQQGuildChannel,
register_sender,
register_qqguild_dms,
register_target_extractor,
)

try:
from nonebot.adapters.qq.event import GuildMessageEvent
from nonebot.adapters.qq.models import Message as ApiMessage
from nonebot.adapters.qq.exception import (
AuditException,
QQAdapterException,
)
from nonebot.adapters.qq.models import (
PostC2CFilesReturn,
PostGroupFilesReturn,
PostC2CMessagesReturn,
PostGroupMessagesReturn,
)
from nonebot.adapters.qq import (
Bot,
Message,
MessageSegment,
MessageCreateEvent,
AtMessageCreateEvent,
C2CMessageCreateEvent,
DirectMessageCreateEvent,
GroupAtMessageCreateEvent,
)

adapter = SupportedAdapters.qq
register_qq = partial(register_ms_adapter, adapter)

MessageFactory.register_adapter_message(adapter, Message)

class QQGuildAuditRejectException(QQAdapterException):
...

class BasePostMessagesReturn(BaseModel):
id: Optional[str] = None
timestamp: Optional[datetime] = None

class QQMessageId(MessageId):
adapter_name: Literal[adapter] = adapter
message_id: str

@register_qq(Text)
def _text(t: Text) -> MessageSegment:
return MessageSegment.text(t.data["text"])

@register_qq(Image)
def _image(i: Image) -> MessageSegment:
if isinstance(i.data["image"], str):
return MessageSegment.image(i.data["image"])
else:
return MessageSegment.file_image(i.data["image"])

@register_qq(Mention)
def _mention(m: Mention) -> MessageSegment:
return MessageSegment.mention_user(m.data["user_id"])

@register_qq(Reply)
def _reply(r: Reply) -> MessageSegment:
assert isinstance(r.data, QQMessageId)
return MessageSegment.reference(r.data.message_id)

@register_target_extractor(GuildMessageEvent)
def extract_message_event(event: Event) -> PlatformTarget:
if isinstance(event, DirectMessageCreateEvent):
assert event.guild_id
assert event.author and event.author.id
return TargetQQGuildDirect(
source_guild_id=int(event.guild_id), recipient_id=int(event.author.id)
)
elif isinstance(event, (MessageCreateEvent, AtMessageCreateEvent)):
assert event.channel_id
return TargetQQGuildChannel(channel_id=int(event.channel_id))
else:
raise ValueError(f"{type(event)} not supported")

@register_target_extractor(C2CMessageCreateEvent)
def extract_c2c_message_event(event: Event) -> PlatformTarget:
assert isinstance(event, C2CMessageCreateEvent)
return TargetQQPrivateOpenId(user_openid=event.author.user_openid)

@register_target_extractor(GroupAtMessageCreateEvent)
def extract_group_at_message_event(event: Event) -> PlatformTarget:
assert isinstance(event, GroupAtMessageCreateEvent)
return TargetQQGroupOpenId(group_openid=event.group_openid)

@register_qqguild_dms(adapter)
async def get_dms(target: TargetQQGuildDirect, bot: BaseBot) -> int:
assert isinstance(bot, Bot)

dms = await bot.post_dms(
recipient_id=str(target.recipient_id),
source_guild_id=str(target.source_guild_id),
)
assert dms.guild_id
return int(dms.guild_id)

class QQReceipt(Receipt):
msg_return: Union[
ApiMessage,
PostC2CMessagesReturn,
PostGroupMessagesReturn,
PostC2CFilesReturn,
PostGroupFilesReturn,
]
adapter_name: Literal[adapter] = adapter

async def revoke(self, hidetip=False):
if not isinstance(self.msg_return, ApiMessage):
raise NotImplementedError("only guild message can be revoked")

assert self.msg_return.channel_id
assert self.msg_return.id
return await self._get_bot().delete_message(
channel_id=self.msg_return.channel_id,
message_id=self.msg_return.id,
hidetip=hidetip,
)

@property
def raw(self):
return self.msg_return

@register_sender(SupportedAdapters.qq)
async def send(
bot,
msg: MessageFactory[MessageSegmentFactory],
target: PlatformTarget,
event: Optional[Event],
at_sender: bool,
reply: bool,
) -> QQReceipt:
assert isinstance(bot, Bot)
assert isinstance(
target,
(TargetQQGuildChannel, TargetQQGuildDirect, TargetQQGroupOpenId, TargetQQPrivateOpenId),
)

full_msg = msg
if event:
assert isinstance(
event,
(GuildMessageEvent, C2CMessageCreateEvent, GroupAtMessageCreateEvent),
)
assert event.author
assert event.id

if isinstance(event, (C2CMessageCreateEvent, GroupAtMessageCreateEvent)):
at_sender = False # qq will add at automatically
reply = False # qq doesnt support reply in group or c2c at this time

full_msg = assamble_message_factory(
msg,
Mention(event.author.id),
Reply(QQMessageId(message_id=event.id)),
at_sender,
reply,
)

# parse Message
message = await full_msg._build(bot)
assert isinstance(message, Message)

try:
if event: # reply to user
msg_return = await bot.send(event, message)
else:
if isinstance(target, TargetQQGuildDirect):
guild_id = await QQGuildDMSManager.aget_guild_id(target, bot)
msg_return = await bot.send_to_dms(
guild_id=str(guild_id),
message=message,
)
elif isinstance(target, TargetQQGuildChannel):
msg_return = await bot.send_to_channel(
channel_id=str(target.channel_id),
message=message,
)
elif isinstance(target, TargetQQPrivateOpenId):
msg_return = await bot.send_to_c2c(
user_id=target.user_id,
message=message,
)
elif isinstance(target, TargetQQGroupOpenId):
msg_return = await bot.send_to_group(
group_id=target.group_id,
message=message,
)
else:
raise ValueError(f"{type(event)} not supported")
except AuditException as e:
audit = await e.get_audit_result()
if type(audit) == "MESSAGE_AUDIT_REJECT":
raise QQGuildAuditRejectException()
if isinstance(event, GuildMessageEvent):
msg_return = ApiMessage(
id=audit.message_id,
channel_id=audit.channel_id,
guild_id=audit.guild_id,
)
else:
msg_return = BasePostMessagesReturn(
id=audit.message_id,
timestamp=audit.audit_time
)

return QQReceipt(bot_id=bot.self_id, msg_return=msg_return)

@register_list_targets(SupportedAdapters.qq)
async def list_targets(bot: BaseBot) -> List[PlatformTarget]:
assert isinstance(bot, Bot)

targets = []

# TODO: 私聊

guilds = await bot.guilds()
for guild in guilds:
channels = await bot.get_channels(guild_id=guild.id) # type: ignore
for channel in channels:
targets.append(
TargetQQGuildChannel(
channel_id=channel.id, # type: ignore
)
)

return targets

except ImportError:
pass
except Exception as e:
raise e
2 changes: 2 additions & 0 deletions nonebot_plugin_saa/registries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
from .platform_send_target import sender_map as sender_map
from .platform_send_target import BotSpecifier as BotSpecifier
from .platform_send_target import TargetQQGroup as TargetQQGroup
from .platform_send_target import TargetQQGroupOpenId as TargetQQGroupOpenId
from .platform_send_target import PlatformTarget as PlatformTarget
from .platform_send_target import extract_target as extract_target
from .platform_send_target import TargetQQPrivate as TargetQQPrivate
from .platform_send_target import TargetQQPrivateOpenId as TargetQQPrivateOpenId
from .platform_send_target import register_sender as register_sender
from .platform_send_target import TargetOB12Unknow as TargetOB12Unknow
from .platform_send_target import QQGuildDMSManager as QQGuildDMSManager
Expand Down
1 change: 1 addition & 0 deletions nonebot_plugin_saa/utils/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class SupportedAdapters(StrEnum):
feishu = "Feishu"
red = "RedProtocol"
dodo = "DoDo"
qq = "QQ"

fake = "fake" # for nonebug

Expand Down
Loading