fix: 群成员禁言对齐官方 members/op 结构——mute_group_members 批量透传、mute_group_member 签名补 member_openid、新增 unmute_group_member

This commit is contained in:
snowy-yang committed 2026-10-03 21:58:02 +08:00
1 parent 321ab70b35
commit 6235f9e393
3 files changed
+59 -9

No files matched your search

+4 -2
View File
@@ -11,7 +11,9 @@
| `post_group_message(group_openid, content="", msg_id=None, msg_seq=1, media=None, event_id=None, markdown=None, keyboard=None, **extra)` | POST /v2/groups/{group_openid}/messages | `msg_id`/`event_id` 二选一即被动回复(`event_id` 支持 GROUP_ADD_ROBOT / INTERACTION_CREATE 等事件);`media` 传上传返回的 `{"file_info": …}`(自动 msg_type=7);`markdown` 为字符串或完整 dict(自动 msg_type=2,与 content 互斥);`keyboard` 为内嵌键盘 `{"content": {"rows": [...]}}` 或 `{"id": …}`;其余字段经 `extra` 透传 |
| `upload_group_file(group_openid, file_type, url, srv_send_msg=False)` | POST /v2/groups/{group_openid}/files | 富媒体上传,返回含 `file_info`;`srv_send_msg=True` 上传即发送 |
| `withdraw_group_message(group_openid, message_id)` | DELETE /v2/groups/{group_openid}/messages/{message_id} | 发出 2 分钟内可撤回 |
| `mute_group_member(group_openid, mute_expire_at)` | POST /v2/groups/{group_openid}/restrict_chat_setting | `mute_expire_at` 为 RFC3339 到期时间;传早于当前的时间即解禁 |
| `mute_group_members(group_openid, members)` | POST /v2/groups/{group_openid}/restrict_chat_setting | 批量禁言/解禁;`members` 每项为 `{"op": "add"/"update"/"del", "member_openid": …, "mute_expire_at": …}`(RFC3339):add 新增禁言、update 更新到期时间、del 解禁(expire 可传空串)。单次最多 20 人、最长禁言 30 天、仅可操作普通成员(群主/管理员/机器人不可);需群管理员身份,60 QPM |
| `mute_group_member(group_openid, member_openid, mute_expire_at)` | 同上 | 单人禁言封装(op=add) |
| `unmute_group_member(group_openid, member_openid)` | 同上 | 单人解禁封装(op=del) |
| `prepare_group_upload(group_openid, file_type, file_size, file_name, md5, sha1, md5_10m)` | POST /v2/groups/{group_openid}/upload_prepare | 大文件分片预上传,返回 `upload_id`/`block_size`/各分片预签名 `parts`;逐片 PUT 后调 `finish_group_upload_part`,全部完成后携 `upload_id` 调 `upload_group_file` 合并;`md5_10m` 为文件前约 10MB 的 MD5 |
| `finish_group_upload_part(group_openid, upload_id, part_index, block_size=None, md5=None)` | POST …/upload_part_finish | 每个分片 PUT 成功后调用确认 |
@@ -42,6 +44,6 @@
| `generate_url_link(callback_data=None)` | 生成机器人分享链接(POST /v2/generate_url_link),返回 `{"data": {"url": …}}`;用户经链接添加时 `callback_data`(≤32 字符)透传给开发者 |
| `respond_interaction(interaction_id, code=0)` | 回应互动事件(PUT /interactions/{id});按钮点击收到后必须回应,否则客户端持续 loading,同一 id 仅可回应一次 |
常量:`MSG_TYPE_TEXT=0`、`MSG_TYPE_MARKDOWN=2`、`MSG_TYPE_MEDIA=7`;`FILE_TYPE_IMAGE=1`、`FILE_TYPE_VIDEO=2`、`FILE_TYPE_VOICE=3`、`FILE_TYPE_FILE=4`;流式 `STREAM_INPUT_APPEND`/`STREAM_INPUT_REPLACE`、`STREAM_GENERATING=1`/`STREAM_FINISHED=10`、`STREAM_CONTENT_TEXT`/`STREAM_CONTENT_MARKDOWN`。
常量:`MSG_TYPE_TEXT=0`、`MSG_TYPE_MARKDOWN=2`、`MSG_TYPE_MEDIA=7`;`FILE_TYPE_IMAGE=1`、`FILE_TYPE_VIDEO=2`、`FILE_TYPE_VOICE=3`、`FILE_TYPE_FILE=4`;流式 `STREAM_INPUT_APPEND`/`STREAM_INPUT_REPLACE`、`STREAM_GENERATING=1`/`STREAM_FINISHED=10`、`STREAM_CONTENT_TEXT`/`STREAM_CONTENT_MARKDOWN`;禁言 `MUTE_OP_ADD`/`MUTE_OP_UPDATE`/`MUTE_OP_DEL`。
按钮构造:`button(label, data, *, type=1, permission=2, style=1)` 生成单个按钮 dict,`keyboard(*rows)` 组装为 `keyboard=` 参数(单按钮独占一行,按钮列表同行并排)。点击回调经 `INTERACTION_CREATE` 事件处理,见[收发消息](/docs/guide/messaging.md#markdown-与按钮msg_type2)。
+35 -5
View File
@@ -28,6 +28,10 @@ STREAM_GENERATING = 1 # input_state:生成中
STREAM_FINISHED = 10 # input_state:生成结束
STREAM_CONTENT_TEXT = "text"
STREAM_CONTENT_MARKDOWN = "markdown"
# 群禁言 members[].op 取值(restrict_chat_setting)
MUTE_OP_ADD = "add"
MUTE_OP_UPDATE = "update"
MUTE_OP_DEL = "del"
def button(
@@ -384,15 +388,41 @@ class V2Api(BaseApi):
"""撤回单聊消息(DELETE /v2/users/{openid}/messages/{message_id})。"""
return await self._withdraw_v2_message(f"/v2/users/{openid}", message_id)
async def mute_group_member(self, group_openid: str, mute_expire_at: str) -> dict:
"""群成员禁言(POST /v2/groups/{group_openid}/restrict_chat_setting)。
async def mute_group_members(
self, group_openid: str, members: list[dict[str, Any]]
) -> dict:
"""批量设置群成员禁言/解禁(POST .../restrict_chat_setting)。
mute_expire_at 为禁言到期时间(RFC3339 格式,如
"2026-01-01T12:00:00+08:00");传当前时间之前即解除禁言。
members 单次最多 20 项,每项为 {"op": MUTE_OP_ADD/MUTE_OP_UPDATE/
MUTE_OP_DEL, "member_openid": ..., "mute_expire_at": RFC3339 时间}:
add 新增禁言、update 更新到期时间、del 解除禁言(expire 可传
空串)。最长禁言 30 天;仅可操作普通成员(群主/管理员/机器人
不可);需群管理员身份,60 QPM。
"""
return await self.post(
f"/v2/groups/{group_openid}/restrict_chat_setting",
json={"mute_expire_at": mute_expire_at},
json={"members": members},
)
async def mute_group_member(
self, group_openid: str, member_openid: str, mute_expire_at: str
) -> dict:
"""禁言单个群成员(mute_group_members 的 op=add 封装)。"""
return await self.mute_group_members(
group_openid,
[
{
"op": MUTE_OP_ADD,
"member_openid": member_openid,
"mute_expire_at": mute_expire_at,
}
],
)
async def unmute_group_member(self, group_openid: str, member_openid: str) -> dict:
"""解除单个群成员禁言(mute_group_members 的 op=del 封装)。"""
return await self.mute_group_members(
group_openid, [{"op": MUTE_OP_DEL, "member_openid": member_openid}]
)
async def generate_url_link(self, callback_data: str | None = None) -> dict:
+20 -2
View File
@@ -116,10 +116,28 @@ async def test_mute_and_schedule_official_field_names():
assert info["remind_type"] == "0"
assert "schedule_info" not in kwargs["json"]
await api.mute_group_member("G1", "2026-01-01T12:00:00+08:00")
await api.mute_group_member("G1", "M1", "2026-01-01T12:00:00+08:00")
method, path, kwargs = cap["call"]
assert (method, path) == ("POST", "/v2/groups/G1/restrict_chat_setting")
assert kwargs["json"] == {"mute_expire_at": "2026-01-01T12:00:00+08:00"}
assert kwargs["json"] == {
"members": [
{
"op": "add",
"member_openid": "M1",
"mute_expire_at": "2026-01-01T12:00:00+08:00",
}
]
}
await api.unmute_group_member("G1", "M1")
assert cap["call"][2]["json"] == {"members": [{"op": "del", "member_openid": "M1"}]}
members = [
{"op": "add", "member_openid": "M1", "mute_expire_at": "t1"},
{"op": "update", "member_openid": "M2", "mute_expire_at": "t2"},
]
await api.mute_group_members("G1", members)
assert cap["call"][2]["json"] == {"members": members}
async def test_me():