乱点提示 @点击者:markdown 内联提及 <@openid>

This commit is contained in:
snowy-yang committed 2026-10-05 10:19:04 +08:00
1 parent 755013b9db
commit 3fed0b0db1
3 files changed
+79 -9

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
# 密钥与本地配置(绝不提交)
.env
# Python 运行产物
__pycache__/
*.pyc
.pytest_cache/
# 虚拟环境
.venv/
# 运行时状态(禁言/验证进度,含成员 openid)
data/
# ZCode 工作目录
.zcode/
# ===== ↑ 以上同步自 .gitignore(「从 .gitignore 同步」只重写以上部分)=====
.git/
.hg/
.svn/
node_modules/
bower_components/
jspm_packages/
site-packages/
venv/
coverage/
htmlcov/
lcov-report/
cmakefiles/
cmake-build-*/
bazel-*/
pods/
deriveddata/
storybook-static/
playwright-report/
test-results/
allure-results/
allure-report/
cdk.out/
*.egg-info/
*.dist-info/
eggs/
pip-wheel-metadata/
wheels/
# ----- ↑ 以上为 ZCode 默认排除规则(自定义规则请写在本行下方,不会被同步/恢复改动)-----
# 自定义规则写在下方(本行提示可删除)
+24 -4
View File
@@ -96,10 +96,26 @@ async def set_member_mute(
return False
async def _reply_text(api: BotApi, group_openid: str, text: str, *, event_id: str | None = None, msg_id: str | None = None) -> None:
"""被动回复文本;窗口过期等失败降级为日志,不影响主流程。"""
async def _reply_text(
api: BotApi, group_openid: str, text: str, *,
event_id: str | None = None, msg_id: str | None = None,
mention: str | None = None,
) -> None:
"""被动回复文本;mention 传 openid 时改为 markdown 内联提及 @对方。
窗口过期等失败降级为日志,不影响主流程。提及必须走 markdown
(纯文本 content 不解析 <@> 语法)。
"""
try:
await api.post_group_message(group_openid, content=text, event_id=event_id, msg_id=msg_id)
if mention:
await api.post_group_message(
group_openid, markdown=f"<@{mention}> {text}",
event_id=event_id, msg_id=msg_id,
)
else:
await api.post_group_message(
group_openid, content=text, event_id=event_id, msg_id=msg_id,
)
except ApiError as e:
logger.warning(f"回复消息失败 (group={group_openid}): {e}")
@@ -194,6 +210,7 @@ async def _handle_foreign_click(
f"这道题不是给你的~(再乱点 {remaining} 次将被禁言 "
f"{cfg.foreign_click_mute_minutes} 分钟)",
event_id=event_id,
mention=member_openid,
)
return
@@ -215,6 +232,7 @@ async def _handle_foreign_click(
api, group_openid,
f"多次乱点他人的验证按钮,已禁言 {cfg.foreign_click_mute_minutes} 分钟。",
event_id=event_id,
mention=member_openid,
)
else:
await _reply_text(
@@ -222,6 +240,7 @@ async def _handle_foreign_click(
"多次乱点他人的验证按钮!禁言未能生效(群主/管理员无法被禁言),"
"请管理员处理。",
event_id=event_id,
mention=member_openid,
)
@@ -350,7 +369,8 @@ def setup(bot: Bot, cfg: AuthConfig) -> VerifyState:
else:
# 旧题/已失效按钮,礼貌忽略不计处罚
await _reply_text(api, inter.group_openid, "这道题不是给你的~",
event_id=event.event_id)
event_id=event.event_id,
mention=inter.group_member_openid)
return
if not is_subject:
await _reply_text(api, inter.group_openid, "这题已经被换掉啦,请回答最新一题",
+8 -5
View File
@@ -142,15 +142,17 @@ def test_foreign_clicker_muted_on_third_click(tmp_path, monkeypatch):
# 每次点击都回应了互动
assert len(fake.interactions_responded) == 3
# 前两次只有警告,第三次才禁言 5 分钟
warns = [m["content"] for m in fake.group_messages[:2]]
assert all("不是给你的" in (c or "") for c in warns)
# 前两次只有警告(markdown 内联提及点击者),第三次才禁言 5 分钟
warns = [(m["markdown"] or "") for m in fake.group_messages[:2]]
assert all("不是给你的" in md for md in warns)
assert all(f"<@MEMBER_B>" in md for md in warns)
mutes = [j for p, j in fake.raw_posts if p.endswith("/restrict_chat_setting")]
assert len(mutes) == 1
entry = mutes[0]["members"][0]
assert entry["op"] == "add"
assert entry["member_openid"] == "MEMBER_B"
assert "已禁言 5 分钟" in (fake.group_messages[-1]["content"] or "")
assert "已禁言 5 分钟" in (fake.group_messages[-1]["markdown"] or "")
assert "<@MEMBER_B>" in (fake.group_messages[-1]["markdown"] or "")
# 被试者的记录不受影响
assert state.get(pending_key("GROUP1", "MEMBER_A")) is not None
state.pop(pending_key("GROUP1", "MEMBER_A"))
@@ -173,6 +175,7 @@ def test_stale_button_click_not_punished(tmp_path, monkeypatch):
await asyncio.sleep(0.03)
assert fake.raw_posts == [] # 三次都不触发禁言
assert all("不是给你的" in (m["content"] or "") for m in fake.group_messages)
assert all("不是给你的" in (m["markdown"] or "") for m in fake.group_messages)
assert all("<@MEMBER_C>" in (m["markdown"] or "") for m in fake.group_messages)
asyncio.run(scenario())