2021-08-04 00:35:31 +08:00
|
|
|
|
import json
|
2021-10-21 00:37:02 +08:00
|
|
|
|
import asyncio
|
2021-01-30 20:40:00 +08:00
|
|
|
|
from io import BytesIO
|
2021-10-21 00:37:02 +08:00
|
|
|
|
from functools import partial
|
2021-01-30 05:58:30 +08:00
|
|
|
|
from ipaddress import IPv4Address
|
2021-10-21 00:37:02 +08:00
|
|
|
|
from typing import Any, Dict, List, Tuple, Union, NoReturn, Optional
|
2021-01-30 05:58:30 +08:00
|
|
|
|
|
|
|
|
|
import httpx
|
2021-08-04 00:35:31 +08:00
|
|
|
|
from loguru import logger
|
2021-01-29 17:37:44 +08:00
|
|
|
|
|
2021-08-04 00:35:31 +08:00
|
|
|
|
from nonebot.config import Config
|
|
|
|
|
from nonebot.typing import overrides
|
2021-10-21 00:37:02 +08:00
|
|
|
|
from nonebot.adapters import Bot as BaseBot
|
|
|
|
|
from nonebot.exception import ApiNotAvailable
|
|
|
|
|
from nonebot.drivers import (Driver, WebSocket, HTTPResponse, ForwardDriver,
|
|
|
|
|
ReverseDriver, HTTPConnection, WebSocketSetup)
|
2021-01-29 17:37:44 +08:00
|
|
|
|
|
2021-01-30 13:36:31 +08:00
|
|
|
|
from .config import Config as MiraiConfig
|
2021-01-30 20:40:00 +08:00
|
|
|
|
from .message import MessageChain, MessageSegment
|
2021-10-21 00:37:02 +08:00
|
|
|
|
from .event import Event, TempMessage, GroupMessage, FriendMessage
|
|
|
|
|
from .utils import Log, process_event, argument_validation, catch_network_error
|
2021-01-30 21:51:51 +08:00
|
|
|
|
|
|
|
|
|
|
2021-01-30 19:11:17 +08:00
|
|
|
|
class SessionManager:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""Bot会话管理器, 提供API主动调用接口"""
|
2021-08-11 22:15:17 +08:00
|
|
|
|
sessions: Dict[int, Tuple[str, httpx.AsyncClient]] = {}
|
2021-01-30 05:58:30 +08:00
|
|
|
|
|
2021-01-30 19:11:17 +08:00
|
|
|
|
def __init__(self, session_key: str, client: httpx.AsyncClient):
|
|
|
|
|
self.session_key, self.client = session_key, client
|
2021-01-30 13:36:31 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@catch_network_error
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def post(self,
|
|
|
|
|
path: str,
|
|
|
|
|
*,
|
2021-02-01 13:19:37 +08:00
|
|
|
|
params: Optional[Dict[str, Any]] = None) -> Any:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
以POST方式主动提交API请求
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``path: str``: 对应API路径
|
|
|
|
|
* ``params: Optional[Dict[str, Any]]``: 请求参数 (无需sessionKey)
|
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
|
|
|
|
- ``Dict[str, Any]``: API 返回值
|
|
|
|
|
"""
|
|
|
|
|
response = await self.client.post(
|
|
|
|
|
path,
|
|
|
|
|
json={
|
|
|
|
|
**(params or {}),
|
|
|
|
|
'sessionKey': self.session_key,
|
|
|
|
|
},
|
|
|
|
|
timeout=3,
|
|
|
|
|
)
|
2021-01-30 13:36:31 +08:00
|
|
|
|
response.raise_for_status()
|
2021-02-01 13:19:37 +08:00
|
|
|
|
return response.json()
|
2021-01-30 13:36:31 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@catch_network_error
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def request(self,
|
|
|
|
|
path: str,
|
|
|
|
|
*,
|
2021-02-01 13:19:37 +08:00
|
|
|
|
params: Optional[Dict[str, Any]] = None) -> Any:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
以GET方式主动提交API请求
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``path: str``: 对应API路径
|
|
|
|
|
* ``params: Optional[Dict[str, Any]]``: 请求参数 (无需sessionKey)
|
|
|
|
|
"""
|
|
|
|
|
response = await self.client.get(
|
|
|
|
|
path,
|
|
|
|
|
params={
|
|
|
|
|
**(params or {}),
|
|
|
|
|
'sessionKey': self.session_key,
|
|
|
|
|
},
|
|
|
|
|
timeout=3,
|
|
|
|
|
)
|
2021-01-30 20:40:00 +08:00
|
|
|
|
response.raise_for_status()
|
2021-02-01 13:19:37 +08:00
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
|
@catch_network_error
|
|
|
|
|
async def upload(self, path: str, *, params: Dict[str, Any]) -> Any:
|
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
以表单(``multipart/form-data``)形式主动提交API请求
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``path: str``: 对应API路径
|
|
|
|
|
* ``params: Dict[str, Any]``: 请求参数 (无需sessionKey)
|
|
|
|
|
"""
|
|
|
|
|
files = {k: v for k, v in params.items() if isinstance(v, BytesIO)}
|
|
|
|
|
form = {k: v for k, v in params.items() if k not in files}
|
2021-07-07 20:39:46 +08:00
|
|
|
|
form['sessionKey'] = self.session_key
|
2021-02-01 13:19:37 +08:00
|
|
|
|
response = await self.client.post(
|
|
|
|
|
path,
|
|
|
|
|
data=form,
|
|
|
|
|
files=files,
|
|
|
|
|
timeout=6,
|
|
|
|
|
)
|
2021-01-30 20:40:00 +08:00
|
|
|
|
response.raise_for_status()
|
2021-02-01 13:19:37 +08:00
|
|
|
|
return response.json()
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-01-30 05:58:30 +08:00
|
|
|
|
@classmethod
|
2021-01-30 19:11:17 +08:00
|
|
|
|
async def new(cls, self_id: int, *, host: IPv4Address, port: int,
|
2021-02-01 13:19:37 +08:00
|
|
|
|
auth_key: str) -> "SessionManager":
|
2021-01-31 16:02:59 +08:00
|
|
|
|
session = cls.get(self_id)
|
|
|
|
|
if session is not None:
|
|
|
|
|
return session
|
|
|
|
|
|
2021-10-21 00:37:02 +08:00
|
|
|
|
client = httpx.AsyncClient(base_url=f'http://{host}:{port}',
|
|
|
|
|
follow_redirects=True)
|
2021-01-30 19:11:17 +08:00
|
|
|
|
response = await client.post('/auth', json={'authKey': auth_key})
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
auth = response.json()
|
|
|
|
|
assert auth['code'] == 0
|
|
|
|
|
session_key = auth['session']
|
|
|
|
|
response = await client.post('/verify',
|
|
|
|
|
json={
|
|
|
|
|
'sessionKey': session_key,
|
|
|
|
|
'qq': self_id
|
|
|
|
|
})
|
|
|
|
|
assert response.json()['code'] == 0
|
2021-08-11 22:15:17 +08:00
|
|
|
|
cls.sessions[self_id] = session_key, client
|
2021-01-31 16:02:59 +08:00
|
|
|
|
|
2021-01-30 19:11:17 +08:00
|
|
|
|
return cls(session_key, client)
|
2021-01-30 05:58:30 +08:00
|
|
|
|
|
2021-01-30 19:11:17 +08:00
|
|
|
|
@classmethod
|
2021-08-11 22:15:17 +08:00
|
|
|
|
def get(cls, self_id: int):
|
2021-01-31 16:02:59 +08:00
|
|
|
|
if self_id not in cls.sessions:
|
|
|
|
|
return None
|
2021-08-11 22:15:17 +08:00
|
|
|
|
key, client = cls.sessions[self_id]
|
2021-01-30 19:11:17 +08:00
|
|
|
|
return cls(key, client)
|
2021-01-30 05:58:30 +08:00
|
|
|
|
|
2021-01-29 17:37:44 +08:00
|
|
|
|
|
2021-02-01 14:24:45 +08:00
|
|
|
|
class Bot(BaseBot):
|
2021-06-10 21:52:20 +08:00
|
|
|
|
r"""
|
2021-02-01 00:01:31 +08:00
|
|
|
|
mirai-api-http 协议 Bot 适配。
|
2021-02-01 13:19:37 +08:00
|
|
|
|
|
|
|
|
|
\:\:\: warning
|
|
|
|
|
API中为了使代码更加整洁, 我们采用了与PEP8相符的命名规则取代Mirai原有的驼峰命名
|
|
|
|
|
|
|
|
|
|
部分字段可能与文档在符号上不一致
|
|
|
|
|
\:\:\:
|
|
|
|
|
|
2021-02-01 00:01:31 +08:00
|
|
|
|
"""
|
2021-01-29 17:37:44 +08:00
|
|
|
|
|
2021-08-04 00:35:31 +08:00
|
|
|
|
_type = 'mirai'
|
|
|
|
|
|
2021-01-29 17:37:44 +08:00
|
|
|
|
@property
|
|
|
|
|
@overrides(BaseBot)
|
|
|
|
|
def type(self) -> str:
|
2021-08-04 00:35:31 +08:00
|
|
|
|
return self._type
|
2021-01-30 13:36:31 +08:00
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
@property
|
|
|
|
|
def api(self) -> SessionManager:
|
|
|
|
|
"""返回该Bot对象的会话管理实例以提供API主动调用"""
|
|
|
|
|
api = SessionManager.get(self_id=int(self.self_id))
|
2021-08-11 22:15:17 +08:00
|
|
|
|
if api is None:
|
|
|
|
|
if isinstance(self.request, WebSocket):
|
|
|
|
|
asyncio.create_task(self.request.close(1000))
|
2021-02-01 01:04:30 +08:00
|
|
|
|
assert api is not None, 'SessionManager has not been initialized'
|
|
|
|
|
return api
|
|
|
|
|
|
2021-01-29 17:37:44 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
@overrides(BaseBot)
|
2021-06-10 21:52:20 +08:00
|
|
|
|
async def check_permission(
|
|
|
|
|
cls, driver: Driver,
|
|
|
|
|
request: HTTPConnection) -> Tuple[Optional[str], HTTPResponse]:
|
|
|
|
|
if isinstance(request, WebSocket):
|
|
|
|
|
return None, HTTPResponse(
|
|
|
|
|
501, b'Websocket connection is not implemented')
|
|
|
|
|
self_id: Optional[str] = request.headers.get('bot')
|
2021-01-30 19:11:17 +08:00
|
|
|
|
if self_id is None:
|
2021-06-10 21:52:20 +08:00
|
|
|
|
return None, HTTPResponse(400, b'Header `Bot` is required.')
|
2021-01-30 19:11:17 +08:00
|
|
|
|
self_id = str(self_id).strip()
|
|
|
|
|
await SessionManager.new(
|
|
|
|
|
int(self_id),
|
|
|
|
|
host=cls.mirai_config.host, # type: ignore
|
|
|
|
|
port=cls.mirai_config.port, #type: ignore
|
|
|
|
|
auth_key=cls.mirai_config.auth_key) # type: ignore
|
2021-06-10 21:52:20 +08:00
|
|
|
|
return self_id, HTTPResponse(204, b'')
|
2021-01-30 05:58:30 +08:00
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
@overrides(BaseBot)
|
2021-08-04 00:35:31 +08:00
|
|
|
|
def register(cls,
|
|
|
|
|
driver: Driver,
|
|
|
|
|
config: "Config",
|
2021-08-12 23:14:45 +08:00
|
|
|
|
qq: Optional[Union[int, List[int]]] = None):
|
2021-01-30 13:36:31 +08:00
|
|
|
|
cls.mirai_config = MiraiConfig(**config.dict())
|
2021-02-01 13:19:37 +08:00
|
|
|
|
if (cls.mirai_config.auth_key and cls.mirai_config.host and
|
|
|
|
|
cls.mirai_config.port) is None:
|
2021-08-04 00:35:31 +08:00
|
|
|
|
raise ApiNotAvailable(cls._type)
|
|
|
|
|
|
2021-01-30 13:36:31 +08:00
|
|
|
|
super().register(driver, config)
|
|
|
|
|
|
2021-08-04 00:35:31 +08:00
|
|
|
|
if not isinstance(driver, ForwardDriver) and qq:
|
|
|
|
|
logger.warning(
|
|
|
|
|
f"Current driver {cls.config.driver} don't support forward connections"
|
|
|
|
|
)
|
|
|
|
|
elif isinstance(driver, ForwardDriver) and qq:
|
2021-08-12 23:14:45 +08:00
|
|
|
|
self_ids = [qq] if isinstance(qq, int) else qq
|
2021-08-04 00:35:31 +08:00
|
|
|
|
|
2021-08-12 23:14:45 +08:00
|
|
|
|
async def url_factory(qq: int):
|
2021-08-04 00:35:31 +08:00
|
|
|
|
assert cls.mirai_config.host and cls.mirai_config.port and cls.mirai_config.auth_key
|
|
|
|
|
session = await SessionManager.new(
|
2021-08-12 23:14:45 +08:00
|
|
|
|
qq,
|
2021-08-04 00:35:31 +08:00
|
|
|
|
host=cls.mirai_config.host,
|
|
|
|
|
port=cls.mirai_config.port,
|
|
|
|
|
auth_key=cls.mirai_config.auth_key)
|
|
|
|
|
return WebSocketSetup(
|
|
|
|
|
adapter=cls._type,
|
|
|
|
|
self_id=str(qq),
|
|
|
|
|
url=(f'ws://{cls.mirai_config.host}:{cls.mirai_config.port}'
|
|
|
|
|
f'/all?sessionKey={session.session_key}'))
|
|
|
|
|
|
2021-08-12 23:14:45 +08:00
|
|
|
|
for self_id in self_ids:
|
|
|
|
|
driver.setup_websocket(partial(url_factory, qq=self_id))
|
|
|
|
|
|
2021-08-04 00:35:31 +08:00
|
|
|
|
elif isinstance(driver, ReverseDriver):
|
|
|
|
|
logger.debug(
|
|
|
|
|
'Param "qq" does not set for mirai adapter, use http post instead'
|
|
|
|
|
)
|
|
|
|
|
|
2021-01-29 17:37:44 +08:00
|
|
|
|
@overrides(BaseBot)
|
2021-08-04 00:35:31 +08:00
|
|
|
|
async def handle_message(self, message: bytes):
|
2021-02-01 16:31:51 +08:00
|
|
|
|
try:
|
2021-02-07 11:52:50 +08:00
|
|
|
|
await process_event(
|
2021-02-01 16:31:51 +08:00
|
|
|
|
bot=self,
|
2021-02-07 11:52:50 +08:00
|
|
|
|
event=Event.new({
|
2021-08-04 00:35:31 +08:00
|
|
|
|
**json.loads(message),
|
2021-02-07 11:52:50 +08:00
|
|
|
|
'self_id': self.self_id,
|
|
|
|
|
}),
|
2021-02-01 16:31:51 +08:00
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
2021-02-07 11:52:50 +08:00
|
|
|
|
Log.error(f'Failed to handle message: {message}', e)
|
2021-01-29 17:37:44 +08:00
|
|
|
|
|
2021-03-31 16:51:09 +08:00
|
|
|
|
@overrides(BaseBot)
|
|
|
|
|
async def _call_api(self, api: str, **data) -> NoReturn:
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2021-01-29 17:37:44 +08:00
|
|
|
|
@overrides(BaseBot)
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def call_api(self, api: str, **data) -> NoReturn:
|
2021-06-10 21:52:20 +08:00
|
|
|
|
r"""
|
2021-02-01 13:50:14 +08:00
|
|
|
|
\:\:\: danger
|
2021-02-01 13:19:37 +08:00
|
|
|
|
由于Mirai的HTTP API特殊性, 该API暂时无法实现
|
2021-02-01 13:50:14 +08:00
|
|
|
|
\:\:\:
|
2021-03-20 14:49:58 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
\:\:\: tip
|
|
|
|
|
你可以使用 ``MiraiBot.api`` 中提供的调用方法来代替
|
|
|
|
|
\:\:\:
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
@overrides(BaseBot)
|
2021-01-30 21:51:51 +08:00
|
|
|
|
def __getattr__(self, key: str) -> NoReturn:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""由于Mirai的HTTP API特殊性, 该API暂时无法实现"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
raise NotImplementedError
|
2021-01-29 17:37:44 +08:00
|
|
|
|
|
|
|
|
|
@overrides(BaseBot)
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def send(self,
|
|
|
|
|
event: Event,
|
2021-01-31 16:02:59 +08:00
|
|
|
|
message: Union[MessageChain, MessageSegment, str],
|
|
|
|
|
at_sender: bool = False):
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-01 13:50:14 +08:00
|
|
|
|
根据 ``event`` 向触发事件的主体发送信息
|
2021-01-31 18:00:32 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``event: Event``: Event对象
|
|
|
|
|
* ``message: Union[MessageChain, MessageSegment, str]``: 要发送的消息
|
2021-02-01 13:50:14 +08:00
|
|
|
|
* ``at_sender: bool``: 是否 @ 事件主体
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
2021-02-07 12:17:21 +08:00
|
|
|
|
if not isinstance(message, MessageChain):
|
2021-01-31 16:02:59 +08:00
|
|
|
|
message = MessageChain(message)
|
2021-01-30 20:40:00 +08:00
|
|
|
|
if isinstance(event, FriendMessage):
|
|
|
|
|
return await self.send_friend_message(target=event.sender.id,
|
|
|
|
|
message_chain=message)
|
|
|
|
|
elif isinstance(event, GroupMessage):
|
2021-01-31 16:02:59 +08:00
|
|
|
|
if at_sender:
|
|
|
|
|
message = MessageSegment.at(event.sender.id) + message
|
|
|
|
|
return await self.send_group_message(group=event.sender.group.id,
|
|
|
|
|
message_chain=message)
|
2021-01-30 20:40:00 +08:00
|
|
|
|
elif isinstance(event, TempMessage):
|
|
|
|
|
return await self.send_temp_message(qq=event.sender.id,
|
|
|
|
|
group=event.sender.group.id,
|
|
|
|
|
message_chain=message)
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError(f'Unsupported event type {event!r}.')
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def send_friend_message(self, target: int,
|
|
|
|
|
message_chain: MessageChain):
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法向指定好友发送消息
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 发送消息目标好友的 QQ 号
|
|
|
|
|
* ``message_chain: MessageChain``: 消息链,是一个消息对象构成的数组
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('sendFriendMessage',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'messageChain': message_chain.export()
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def send_temp_message(self, qq: int, group: int,
|
|
|
|
|
message_chain: MessageChain):
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法向临时会话对象发送消息
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``qq: int``: 临时会话对象 QQ 号
|
|
|
|
|
* ``group: int``: 临时会话群号
|
|
|
|
|
* ``message_chain: MessageChain``: 消息链,是一个消息对象构成的数组
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('sendTempMessage',
|
|
|
|
|
params={
|
|
|
|
|
'qq': qq,
|
|
|
|
|
'group': group,
|
|
|
|
|
'messageChain': message_chain.export()
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 21:51:51 +08:00
|
|
|
|
async def send_group_message(self,
|
|
|
|
|
group: int,
|
|
|
|
|
message_chain: MessageChain,
|
|
|
|
|
quote: Optional[int] = None):
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法向指定群发送消息
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``group: int``: 发送消息目标群的群号
|
|
|
|
|
* ``message_chain: MessageChain``: 消息链,是一个消息对象构成的数组
|
|
|
|
|
* ``quote: Optional[int]``: 引用一条消息的 message_id 进行回复
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('sendGroupMessage',
|
|
|
|
|
params={
|
2021-01-30 21:51:51 +08:00
|
|
|
|
'group': group,
|
|
|
|
|
'messageChain': message_chain.export(),
|
|
|
|
|
'quote': quote
|
2021-01-30 20:40:00 +08:00
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def recall(self, target: int):
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法撤回指定消息。对于bot发送的消息,有2分钟时间限制。对于撤回群聊中群员的消息,需要有相应权限
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 需要撤回的消息的message_id
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('recall', params={'target': target})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def send_image_message(self, target: int, qq: int, group: int,
|
2021-01-31 22:43:43 +08:00
|
|
|
|
urls: List[str]) -> List[str]:
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法向指定对象(群或好友)发送图片消息
|
|
|
|
|
除非需要通过此手段获取image_id,否则不推荐使用该接口
|
|
|
|
|
|
|
|
|
|
> 当qq和group同时存在时,表示发送临时会话图片,qq为临时会话对象QQ号,group为临时会话发起的群号
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
2021-01-31 22:43:43 +08:00
|
|
|
|
* ``target: int``: 发送对象的QQ号或群号,可能存在歧义
|
|
|
|
|
* ``qq: int``: 发送对象的QQ号
|
|
|
|
|
* ``group: int``: 发送对象的群号
|
|
|
|
|
* ``urls: List[str]``: 是一个url字符串构成的数组
|
2021-01-31 18:00:32 +08:00
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
2021-01-31 22:43:43 +08:00
|
|
|
|
- ``List[str]``: 一个包含图片imageId的数组
|
2021-01-31 18:00:32 +08:00
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('sendImageMessage',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'qq': qq,
|
|
|
|
|
'group': group,
|
|
|
|
|
'urls': urls
|
2021-02-01 13:19:37 +08:00
|
|
|
|
})
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def upload_image(self, type: str, img: BytesIO):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法上传图片文件至服务器并返回Image_id
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``type: str``: "friend" 或 "group" 或 "temp"
|
|
|
|
|
* ``img: BytesIO``: 图片的BytesIO对象
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.upload('uploadImage',
|
2021-02-01 13:19:37 +08:00
|
|
|
|
params={
|
|
|
|
|
'type': type,
|
|
|
|
|
'img': img
|
|
|
|
|
})
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def upload_voice(self, type: str, voice: BytesIO):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法上传语音文件至服务器并返回voice_id
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``type: str``: 当前仅支持 "group"
|
|
|
|
|
* ``voice: BytesIO``: 语音的BytesIO对象
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.upload('uploadVoice',
|
2021-02-01 13:19:37 +08:00
|
|
|
|
params={
|
|
|
|
|
'type': type,
|
|
|
|
|
'voice': voice
|
|
|
|
|
})
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-31 22:43:43 +08:00
|
|
|
|
async def fetch_message(self, count: int = 10):
|
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot接收到的最老消息和最老各类事件
|
|
|
|
|
(会从MiraiApiHttp消息记录中删除)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``count: int``: 获取消息和事件的数量
|
|
|
|
|
"""
|
|
|
|
|
return await self.api.request('fetchMessage', params={'count': count})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-31 22:43:43 +08:00
|
|
|
|
async def fetch_latest_message(self, count: int = 10):
|
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot接收到的最新消息和最新各类事件
|
|
|
|
|
(会从MiraiApiHttp消息记录中删除)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``count: int``: 获取消息和事件的数量
|
|
|
|
|
"""
|
|
|
|
|
return await self.api.request('fetchLatestMessage',
|
|
|
|
|
params={'count': count})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-31 22:43:43 +08:00
|
|
|
|
async def peek_message(self, count: int = 10):
|
|
|
|
|
"""
|
|
|
|
|
:说明:
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-01-31 22:43:43 +08:00
|
|
|
|
使用此方法获取bot接收到的最老消息和最老各类事件
|
2021-03-20 14:49:58 +08:00
|
|
|
|
(不会从MiraiApiHttp消息记录中删除)
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-01-31 22:43:43 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``count: int``: 获取消息和事件的数量
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.request('peekMessage', params={'count': count})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-31 22:43:43 +08:00
|
|
|
|
async def peek_latest_message(self, count: int = 10):
|
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot接收到的最新消息和最新各类事件
|
|
|
|
|
(不会从MiraiApiHttp消息记录中删除)
|
2021-03-20 14:49:58 +08:00
|
|
|
|
|
2021-01-31 22:43:43 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``count: int``: 获取消息和事件的数量
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.request('peekLatestMessage',
|
|
|
|
|
params={'count': count})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def messsage_from_id(self, id: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
通过messageId获取一条被缓存的消息
|
|
|
|
|
使用此方法获取bot接收到的消息和各类事件
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``id: int``: 获取消息的message_id
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.request('messageFromId', params={'id': id})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def count_message(self):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot接收并缓存的消息总数,注意不包含被删除的
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.request('countMessage')
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def friend_list(self) -> List[Dict[str, Any]]:
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot的好友列表
|
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
|
|
|
|
- ``List[Dict[str, Any]]``: 返回的好友列表数据
|
|
|
|
|
"""
|
2021-02-01 13:19:37 +08:00
|
|
|
|
return await self.api.request('friendList')
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def group_list(self) -> List[Dict[str, Any]]:
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot的群列表
|
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
|
|
|
|
- ``List[Dict[str, Any]]``: 返回的群列表数据
|
|
|
|
|
"""
|
2021-02-01 13:19:37 +08:00
|
|
|
|
return await self.api.request('groupList')
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def member_list(self, target: int) -> List[Dict[str, Any]]:
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取bot指定群种的成员列表
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
|
|
|
|
- ``List[Dict[str, Any]]``: 返回的群成员列表数据
|
|
|
|
|
"""
|
2021-02-01 13:19:37 +08:00
|
|
|
|
return await self.api.request('memberList', params={'target': target})
|
2021-01-30 20:40:00 +08:00
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def mute(self, target: int, member_id: int, time: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法指定群禁言指定群员(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
* ``member_id: int``: 指定群员QQ号
|
|
|
|
|
* ``time: int``: 禁言时长,单位为秒,最多30天
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('mute',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'memberId': member_id,
|
|
|
|
|
'time': time
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def unmute(self, target: int, member_id: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法指定群解除群成员禁言(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
* ``member_id: int``: 指定群员QQ号
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('unmute',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'memberId': member_id
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def kick(self, target: int, member_id: int, msg: str):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法移除指定群成员(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
* ``member_id: int``: 指定群员QQ号
|
|
|
|
|
* ``msg: str``: 信息
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('kick',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'memberId': member_id,
|
|
|
|
|
'msg': msg
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def quit(self, target: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-03-20 14:49:58 +08:00
|
|
|
|
使用此方法使Bot退出群聊
|
2021-01-31 22:43:43 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 退出的群号
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('quit', params={'target': target})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def mute_all(self, target: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法令指定群进行全体禁言(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('muteAll', params={'target': target})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def unmute_all(self, target: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法令指定群解除全体禁言(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('unmuteAll', params={'target': target})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def group_config(self, target: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取群设置
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
|
|
|
|
.. code-block:: json
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
"name": "群名称",
|
|
|
|
|
"announcement": "群公告",
|
|
|
|
|
"confessTalk": true,
|
|
|
|
|
"allowMemberInvite": true,
|
|
|
|
|
"autoApprove": true,
|
|
|
|
|
"anonymousChat": true
|
|
|
|
|
}
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.request('groupConfig', params={'target': target})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def modify_group_config(self, target: int, config: Dict[str, Any]):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法修改群设置(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
* ``config: Dict[str, Any]``: 群设置, 格式见 ``group_config`` 的返回值
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('groupConfig',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'config': config
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def member_info(self, target: int, member_id: int):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法获取群员资料
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
* ``member_id: int``: 群员QQ号
|
|
|
|
|
|
|
|
|
|
:返回:
|
|
|
|
|
|
|
|
|
|
.. code-block:: json
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
"name": "群名片",
|
|
|
|
|
"specialTitle": "群头衔"
|
|
|
|
|
}
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.request('memberInfo',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'memberId': member_id
|
|
|
|
|
})
|
|
|
|
|
|
2021-02-01 13:19:37 +08:00
|
|
|
|
@argument_validation
|
2021-01-30 20:40:00 +08:00
|
|
|
|
async def modify_member_info(self, target: int, member_id: int,
|
|
|
|
|
info: Dict[str, Any]):
|
2021-01-31 22:43:43 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
使用此方法修改群员资料(需要有相关权限)
|
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 指定群的群号
|
|
|
|
|
* ``member_id: int``: 群员QQ号
|
|
|
|
|
* ``info: Dict[str, Any]``: 群员资料, 格式见 ``member_info`` 的返回值
|
|
|
|
|
"""
|
2021-01-30 20:40:00 +08:00
|
|
|
|
return await self.api.post('memberInfo',
|
|
|
|
|
params={
|
|
|
|
|
'target': target,
|
|
|
|
|
'memberId': member_id,
|
|
|
|
|
'info': info
|
|
|
|
|
})
|