102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from collections import deque
|
|
from typing import Any
|
|
|
|
from loguru import logger
|
|
|
|
from opc.channels.provider_base import OptionalDependencyChannel
|
|
from opc.core.models import SystemMessage
|
|
|
|
|
|
class QQChannel(OptionalDependencyChannel):
|
|
name = "qq"
|
|
required_package = "botpy"
|
|
delivery_mode = "socket"
|
|
|
|
def __init__(self, config: Any, bus: Any):
|
|
super().__init__(config, bus)
|
|
self._client: Any = None
|
|
self._processed_ids: deque[str] = deque(maxlen=1000)
|
|
self._msg_seq: int = 1
|
|
|
|
def get_required_config_fields(self) -> list[str]:
|
|
return ["app_id", "secret"]
|
|
|
|
def normalize_event(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
author = payload.get("author", {})
|
|
sender = str(author.get("id", payload.get("openid", "")))
|
|
return {
|
|
"sender_id": sender,
|
|
"chat_id": str(payload.get("group_openid", payload.get("channel_id", sender))),
|
|
"content": str(payload.get("content", "") or ""),
|
|
"thread_id": "",
|
|
"reply_to": str(payload.get("id", "") or ""),
|
|
"metadata": {"message_id": str(payload.get("id", "") or "")},
|
|
}
|
|
|
|
async def start(self) -> None:
|
|
await super().start()
|
|
import botpy
|
|
|
|
self._client = self._build_client(botpy)
|
|
await self._client.start(appid=self.config.app_id, secret=self.config.secret)
|
|
|
|
def _build_client(self, botpy_module: Any) -> Any:
|
|
intents = botpy_module.Intents(public_messages=True, direct_message=True)
|
|
channel = self
|
|
|
|
class OPCQQClient(botpy_module.Client):
|
|
def __init__(self) -> None:
|
|
super().__init__(intents=intents, ext_handlers=False)
|
|
|
|
async def on_ready(self) -> None:
|
|
logger.info("qq bot ready")
|
|
|
|
async def on_c2c_message_create(self, message: Any) -> None:
|
|
await channel._on_message(message)
|
|
|
|
async def on_direct_message_create(self, message: Any) -> None:
|
|
await channel._on_message(message)
|
|
|
|
return OPCQQClient()
|
|
|
|
async def stop(self) -> None:
|
|
if self._client is not None:
|
|
try:
|
|
await self._client.close()
|
|
except Exception:
|
|
logger.exception("qq client close failed")
|
|
self._client = None
|
|
await super().stop()
|
|
|
|
async def _on_message(self, message: Any) -> None:
|
|
if getattr(message, "id", "") in self._processed_ids:
|
|
return
|
|
self._processed_ids.append(getattr(message, "id", ""))
|
|
author = getattr(message, "author", None)
|
|
payload = {
|
|
"id": str(getattr(message, "id", "") or ""),
|
|
"author": {
|
|
"id": str(getattr(author, "id", "") or getattr(author, "user_openid", "") or ""),
|
|
},
|
|
"content": str(getattr(message, "content", "") or ""),
|
|
}
|
|
await self.publish_normalized(self.normalize_event(payload))
|
|
|
|
async def send(self, message: SystemMessage) -> None:
|
|
await super().send(message)
|
|
if self._client is None:
|
|
logger.warning("qq client not connected")
|
|
return
|
|
metadata = dict(message.metadata or {})
|
|
chat_id = str(metadata.get("chat_id") or message.session_id or "")
|
|
self._msg_seq += 1
|
|
await self._client.api.post_c2c_message(
|
|
openid=chat_id,
|
|
msg_type=0,
|
|
content=message.content,
|
|
msg_id=str(metadata.get("reply_to", "") or metadata.get("message_id", "") or ""),
|
|
msg_seq=self._msg_seq,
|
|
)
|