Skip to content

Commit 3868328

Browse files
authored
feat: 更新鉴权方式,并增加群和好友发消息接口。 (#156)
* feat: 更新鉴权方式,并增加群和好友发送消息、富媒体消息接口 1. 更新鉴权方式:采用appid + 密钥 2. 增加群和好友发送消息(包括富媒体消息) 接口。 3. 修复发现的缩进问题
1 parent 636f98a commit 3868328

38 files changed

+521
-81
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/ambv/black
3-
rev: 21.12b0
3+
rev: 23.11.0
44
hooks:
55
- id: black
66
args: [-l 120]

botpy/api.py

Lines changed: 138 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
forum,
2323
)
2424

25-
2625
class BotAPI:
2726
"""
2827
机器人相关的API接口类
@@ -195,11 +194,11 @@ async def get_guild_member(self, guild_id: str, user_id: str) -> user.Member:
195194
return await self._http.request(route)
196195

197196
async def get_delete_member(
198-
self,
199-
guild_id: str,
200-
user_id: str,
201-
add_blacklist: bool = False,
202-
delete_history_msg_days: int = 0
197+
self,
198+
guild_id: str,
199+
user_id: str,
200+
add_blacklist: bool = False,
201+
delete_history_msg_days: int = 0
203202
) -> str:
204203
"""
205204
删除频道成员
@@ -248,8 +247,8 @@ async def get_guild_members(self, guild_id: str, after: str = "0", limit: int =
248247
)
249248
return await self._http.request(route, params=params)
250249

251-
async def get_guild_role_members(
252-
self, guild_id: str, role_id: str, start_index: str = "0", limit: int = 1
250+
async def get_guild_role_members(
251+
self, guild_id: str, role_id: str, start_index: str = "0", limit: int = 1
253252
) -> Dict[str, Union[List[user.Member], str]]:
254253
"""
255254
获取频道身份组成员列表。
@@ -459,7 +458,7 @@ async def update_channel_role_permissions(
459458
Returns:
460459
成功执行返回`None`。
461460
"""
462-
payload = {"add": str(add.value) if add else None, "remove": str(remove.value) if remove else None}
461+
payload = {"add": str(add.value) if add else None, "remove": str(remove.value) if remove else None}
463462

464463
route = Route(
465464
"PUT", "/channels/{channel_id}/roles/{role_id}/permissions", channel_id=channel_id, role_id=role_id
@@ -1313,3 +1312,133 @@ async def delete_thread(self, channel_id: str, thread_id: str) -> str:
13131312
"DELETE", "/channels/{channel_id}/threads/{thread_id}", channel_id=channel_id, thread_id=thread_id
13141313
)
13151314
return await self._http.request(route)
1315+
1316+
async def post_group_message(
1317+
self,
1318+
group_openid: str,
1319+
msg_type: int = 0,
1320+
content: str = None,
1321+
embed: message.Embed = None,
1322+
ark: message.Ark = None,
1323+
message_reference: message.Reference = None,
1324+
media: message.Media = None,
1325+
msg_id: str = None,
1326+
event_id: str = None,
1327+
markdown: message.MarkdownPayload = None,
1328+
keyboard: message.Keyboard = None,
1329+
) -> message.Message:
1330+
"""
1331+
发送消息。
1332+
1333+
注意:
1334+
- 要求操作人在该群具有发送消息的权限。
1335+
- 发送成功之后,会触发一个创建消息的事件。
1336+
- 被动回复消息有效期为 5 分钟
1337+
- 发送消息接口要求机器人接口需要链接到websocket gateway 上保持在线状态
1338+
1339+
Args:
1340+
group_openid (str): 您要将消息发送到的群的 ID。
1341+
msg_type (int): 消息类型:0 是文本,1 图文混排,2 是 markdown,3 ark,4 embed,7 media 富媒体
1342+
content (str): 消息的文本内容。
1343+
embed (message.Embed): embed 消息,一种特殊的 ark
1344+
ark (message.Ark): ark 模版消息
1345+
message_reference (message.Reference): 对消息的引用。
1346+
media (message.Media): 富媒体消息
1347+
msg_id (str): 您要回复的消息的 ID。
1348+
event_id (str): 您要回复的消息的事件 ID。
1349+
markdown (message.MarkdownPayload): markdown 消息
1350+
keyboard (message.Keyboard): keyboard 消息
1351+
1352+
Returns:
1353+
message.Message: 一个消息字典对象。
1354+
"""
1355+
payload = locals()
1356+
payload.pop("self", None)
1357+
route = Route("POST", "/v2/groups/{group_openid}/messages", group_openid=group_openid)
1358+
return await self._http.request(route, json=payload)
1359+
1360+
async def post_c2c_message(
1361+
self,
1362+
openid: str,
1363+
msg_type: int = 0,
1364+
content: str = None,
1365+
embed: message.Embed = None,
1366+
ark: message.Ark = None,
1367+
message_reference: message.Reference = None,
1368+
media: message.Media = None,
1369+
msg_id: str = None,
1370+
event_id: str = None,
1371+
markdown: message.MarkdownPayload = None,
1372+
keyboard: message.Keyboard = None,
1373+
) -> message.Message:
1374+
"""
1375+
发送消息。
1376+
1377+
注意:
1378+
- 要求操作人具有发送消息的权限。
1379+
- 发送成功之后,会触发一个创建消息的事件。
1380+
- 被动回复消息有效期为 5 分钟
1381+
- 发送消息接口要求机器人接口需要链接到websocket gateway 上保持在线状态
1382+
1383+
Args:
1384+
openid (str): 您要将消息发送到的用户的 ID。
1385+
msg_type (int): 消息类型:0 是文本,1 图文混排,2 是 markdown,3 ark,4 embed,7 media 富媒体
1386+
content (str): 消息的文本内容。
1387+
embed (message.Embed): embed 消息,一种特殊的 ark
1388+
ark (message.Ark): ark 模版消息
1389+
message_reference (message.Reference): 对消息的引用。
1390+
media (message.Media): 富媒体消息
1391+
msg_id (str): 您要回复的消息的 ID。
1392+
event_id (str): 您要回复的消息的事件 ID。
1393+
markdown (message.MarkdownPayload): markdown 消息
1394+
keyboard (message.Keyboard): keyboard 消息
1395+
1396+
Returns:
1397+
message.Message: 一个消息字典对象。
1398+
"""
1399+
payload = locals()
1400+
payload.pop("self", None)
1401+
route = Route("POST", "/v2/users/{openid}/messages", openid=openid)
1402+
return await self._http.request(route, json=payload)
1403+
1404+
async def post_group_file(
1405+
self,
1406+
group_openid: str,
1407+
file_type: int,
1408+
url: str,
1409+
srv_send_msg: bool = False,
1410+
) -> message.Media:
1411+
"""
1412+
上传/发送群聊图片
1413+
1414+
Args:
1415+
file_type (int): 媒体类型:1 图片png/jpg,2 视频mp4,3 语音silk,4 文件(暂不开放)
1416+
url (str): 需要发送媒体资源的url
1417+
srv_send_msg (bool): 设置 true 会直接发送消息到目标端,且会占用主动消息频次
1418+
"""
1419+
payload = locals()
1420+
payload.pop("self", None)
1421+
route = Route("POST", "/v2/groups/{group_openid}/files", group_openid=group_openid)
1422+
return await self._http.request(route, json=payload)
1423+
1424+
async def post_c2c_file(
1425+
self,
1426+
openid: str,
1427+
file_type: int,
1428+
url: str,
1429+
srv_send_msg: bool = False,
1430+
) -> message.Media:
1431+
"""
1432+
上传/发送c2c图片
1433+
1434+
Args:
1435+
file_type (int): 媒体类型:1 图片png/jpg,2 视频mp4,3 语音silk,4 文件(暂不开放)
1436+
url (str): 需要发送媒体资源的url
1437+
srv_send_msg (bool): 设置 true 会直接发送消息到目标端,且会占用主动消息频次
1438+
"""
1439+
payload = locals()
1440+
payload.pop("self", None)
1441+
route = Route("POST", "/v2/users/{openid}/files", openid=openid)
1442+
return await self._http.request(route, json=payload)
1443+
1444+

botpy/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,20 +133,20 @@ async def runner():
133133
except KeyboardInterrupt:
134134
return
135135

136-
async def start(self, appid: str, token: str, ret_coro: bool = False) -> Optional[Coroutine]:
136+
async def start(self, appid: str, secret: str, ret_coro: bool = False) -> Optional[Coroutine]:
137137
"""机器人开始执行
138138
139139
参数
140140
------------
141141
appid: :class:`str`
142142
机器人 appid
143-
token: :class:`str`
144-
机器人 token
143+
secret: :class:`str`
144+
机器人 secret
145145
ret_coro: :class:`bool`
146146
是否需要返回协程对象
147147
"""
148148
# login后再进行后面的操作
149-
token = Token(appid, token)
149+
token = Token(appid, secret)
150150
self.ret_coro = ret_coro
151151

152152
if self.loop is _loop:

botpy/connection.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .channel import Channel
66
from .guild import Guild
77
from .interaction import Interaction
8-
from .message import Message, DirectMessage, MessageAudit
8+
from .message import C2CMessage, GroupMessage, Message, DirectMessage, MessageAudit
99
from .user import Member
1010
from .reaction import Reaction
1111
from .audio import Audio, PublicAudio
@@ -200,6 +200,15 @@ def parse_public_message_delete(self, payload):
200200
_message = Message(self.api, payload.get('id', None), payload.get('d', {}))
201201
self._dispatch("public_message_delete", _message)
202202

203+
# botpy.flags.Intents.public_messages
204+
def parse_group_at_message_create(self, payload):
205+
_message = GroupMessage(self.api, payload.get("id", None), payload.get("d", {}))
206+
self._dispatch("group_at_message_create", _message)
207+
208+
def parse_c2c_message_create(self, payload):
209+
_message = C2CMessage(self.api, payload.get("id", None), payload.get("d", {}))
210+
self._dispatch("c2c_message_create", _message)
211+
203212
# botpy.flags.Intents.forums
204213
def parse_forum_thread_create(self, payload):
205214
_forum = Thread(self.api, payload.get('id', None), payload.get('d', {}))

botpy/ext/command_util.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,30 @@
11
# -*- coding: utf-8 -*-
22
from functools import wraps
3-
from typing import Union
4-
5-
from botpy import BotAPI
6-
from botpy.message import Message
3+
from botpy.message import BaseMessage
74

85

96
class Commands:
107
"""
118
指令装饰器
129
1310
Args:
14-
name (Union[tuple, str]): 字符串元组或单个字符串
11+
args (tuple): 字符串元组
1512
"""
1613

17-
def __init__(self, name: Union[tuple, str]):
18-
self.commands = name
14+
def __init__(self, *args):
15+
self.commands = args
1916

2017
def __call__(self, func):
2118
@wraps(func)
2219
async def decorated(*args, **kwargs):
23-
api: BotAPI = kwargs["api"]
24-
message: Message = kwargs["message"]
25-
if isinstance(self.commands, tuple):
26-
for command in self.commands:
27-
if command in message.content:
28-
# 分割指令后面的指令参数
29-
params = message.content.split(command)[1].strip()
30-
return await func(api=api, message=message, params=params)
31-
elif self.commands in message.content:
32-
# 分割指令后面的指令参数
33-
params = message.content.split(self.commands)[1].strip()
34-
return await func(api=api, message=message, params=params)
35-
else:
36-
return False
20+
message: BaseMessage = kwargs["message"]
21+
for command in self.commands:
22+
if command in message.content:
23+
# 分割指令后面的指令参数
24+
params = message.content.split(command)[1].strip()
25+
kwargs["params"] = params
26+
return await func(*args, **kwargs)
27+
return False
3728

3829
return decorated
3930

botpy/flags.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def __repr__(self) -> str:
104104
@fill_with_flags()
105105
class Intents(BaseFlags):
106106
"""
107+
public_messages 群/C2C公域消息事件
107108
public_guild_messages 公域消息事件
108109
guild_messages 消息事件 (仅 私域 机器人能够设置此 intents)
109110
direct_message 私信事件
@@ -139,6 +140,7 @@ def all(cls):
139140
self.guild_message_reactions = True
140141
self.direct_message = True
141142
self.message_audit = True
143+
self.public_messages = True
142144
self.public_guild_messages = True
143145
self.audio_or_live_channel_member = True
144146
self.open_forum_event = True
@@ -315,7 +317,18 @@ def open_forum_event(self):
315317
316318
"""
317319
return 1 << 18
320+
321+
@Flag
322+
def public_messages(self):
323+
""":class:`bool`: 是否打开公域群/C2C消息事件的监听.
324+
325+
通过增加`client`的`on_xx`事件可以获取事件下发的数据:
318326
327+
- :func:`on_group_at_message_create` // 当收到群@机器人的消息时
328+
- :func:`on_c2c_message_create` // 当收到c2c的消息时
329+
330+
"""
331+
return 1 << 25
319332

320333
@fill_with_flags()
321334
class Permission(BaseFlags):

botpy/gateway.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ def __init__(self, session: Session, _connection: ConnectionSession):
4545
self._parser = _connection.parser
4646
self._can_reconnect = True
4747
self._INVALID_RECONNECT_CODE = [9001, 9005]
48+
self._AUTH_FAIL_CODE = [4004]
4849

4950
async def on_error(self, exception: BaseException):
5051
_log.error("[botpy] websocket连接: %s, 异常信息 : %s" % (self._conn, exception))
5152
traceback.print_exc()
5253

5354
async def on_closed(self, close_status_code, close_msg):
5455
_log.info("[botpy] 关闭, 返回码: %s" % close_status_code + ", 返回信息: %s" % close_msg)
56+
if close_status_code in self._AUTH_FAIL_CODE:
57+
_log.info("[botpy] 鉴权失败,重置token...")
58+
self._session["token"].access_token = None
5559
# 这种不能重新链接
5660
if close_status_code in self._INVALID_RECONNECT_CODE or not self._can_reconnect:
5761
_log.info("[botpy] 无法重连,创建新连接!")
@@ -134,7 +138,7 @@ async def ws_identify(self):
134138
self._session["intent"] = 1
135139

136140
_log.info("[botpy] 鉴权中...")
137-
141+
await self._session["token"].check_token()
138142
payload = {
139143
"op": self.WS_IDENTITY,
140144
"d": {
@@ -167,7 +171,7 @@ async def ws_resume(self):
167171
websocket重连
168172
"""
169173
_log.info("[botpy] 重连启动...")
170-
174+
await self._session["token"].check_token()
171175
payload = {
172176
"op": self.WS_RESUME,
173177
"d": {

0 commit comments

Comments
 (0)