feat: open bot access and guide invalid delivery queries
This commit is contained in:
@@ -44,16 +44,17 @@ The bridge extracts exactly one eRob model and one positive quantity from either
|
||||
a compact input or a natural-language message. Text such as “请查询
|
||||
eRob142H100I-BHM-18ET[V4],这个数量20台的交期” is accepted. The bridge stops when
|
||||
the model is missing, multiple different models are present, the quantity is
|
||||
missing, or multiple possible quantities remain ambiguous. These non-triggering
|
||||
messages are ignored silently: the bridge sends no Feishu reply, makes no API
|
||||
request, and appends no record. Only a message containing exactly one complete,
|
||||
rule-valid eRob model and exactly one positive quantity enters the query workflow.
|
||||
This gate applies even when the bot receives every message in a group; an @mention
|
||||
alone does not bypass it. After a valid initial query, the same sender in the same
|
||||
chat may ask whether there are other candidate models. The bridge keeps that
|
||||
continuation context in memory for six hours, never shares it across senders or
|
||||
chats, and stays silent if no matching context exists. A bridge restart clears
|
||||
the context, so the sender must resend the complete model and quantity. The
|
||||
missing, or multiple possible quantities remain ambiguous. Invalid private messages
|
||||
receive a Chinese usage guide with a complete model-and-quantity example. In groups,
|
||||
invalid text mentioning eRob, AM516, 交期, or 候选型号 receives the same guide;
|
||||
unrelated group messages stay silent. Private images, files, and voice messages
|
||||
receive a request to send text. Guidance never calls the prediction API, appends
|
||||
a record, or adds a received reaction. All human users who can access the Feishu app may use the bot; legacy FEISHU_BOT_ALLOWED_OPEN_IDS settings are ignored.
|
||||
Only one complete, rule-valid model and one positive quantity enter the query workflow.
|
||||
After a valid initial query, the same sender in the same chat may ask for more
|
||||
candidates. Context lasts six hours and is cleared on restart. Without context,
|
||||
private continuation requests and relevant group requests receive instructions
|
||||
to resend the model and quantity; unrelated group text remains silent. The
|
||||
continuation wording is flexible: `还有吗`, `有没有别的`, `再推荐几个`, `继续`,
|
||||
`下一批`, and `more candidates` are examples rather than fixed commands. The
|
||||
bridge still requires both continuation intent and valid context, so unrelated
|
||||
|
||||
@@ -242,11 +242,7 @@ def parse_request(text: str) -> RequestInput:
|
||||
|
||||
|
||||
def parse_trigger_request(text: str) -> RequestInput | None:
|
||||
"""Return a query only when one complete model and one quantity are present.
|
||||
|
||||
Feishu group messages that do not satisfy the complete AM516 input contract
|
||||
are unrelated traffic from the bridge's point of view and must stay silent.
|
||||
"""
|
||||
"""Return a query only when one complete model and one quantity are present."""
|
||||
try:
|
||||
return parse_request(text)
|
||||
except InputError:
|
||||
@@ -993,14 +989,6 @@ def message_text(data: lark.im.v1.P2ImMessageReceiveV1) -> str | None:
|
||||
return text if isinstance(text, str) else None
|
||||
|
||||
|
||||
def is_allowed_sender(data: lark.im.v1.P2ImMessageReceiveV1) -> bool:
|
||||
allowed = {item.strip() for item in os.getenv("FEISHU_BOT_ALLOWED_OPEN_IDS", "").split(",") if item.strip()}
|
||||
if not allowed:
|
||||
return True
|
||||
sender_id = data.event.sender.sender_id
|
||||
return bool(sender_id and sender_id.open_id in allowed)
|
||||
|
||||
|
||||
def post_paragraph(line: str) -> list[dict[str, Any]]:
|
||||
element: dict[str, Any] = {"tag": "text", "text": line or " "}
|
||||
if (
|
||||
@@ -1102,10 +1090,45 @@ _seen_message_ids: set[str] = set()
|
||||
_seen_message_lock = threading.Lock()
|
||||
|
||||
|
||||
def input_guidance(data: lark.im.v1.P2ImMessageReceiveV1, text: str | None) -> str | None:
|
||||
"""Help direct-message users and relevant group queries without calling the API."""
|
||||
message = data.event.message
|
||||
normalized = unicodedata.normalize("NFKC", text or "").lower()
|
||||
if getattr(message, "chat_type", None) != "p2p" and not any(
|
||||
word in normalized for word in ("erob", "am516", "交期", "候选型号")
|
||||
):
|
||||
return None
|
||||
if text and is_candidate_continuation_request(text):
|
||||
reason = "当前没有可继续的查询,请重新发送完整型号和数量。"
|
||||
elif text:
|
||||
try:
|
||||
parse_request(text)
|
||||
except InputError as exc:
|
||||
reason = str(exc)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
reason = "请使用文字发送型号和数量,暂不支持从图片、文件或语音中识别。"
|
||||
return (
|
||||
f"AM516 交期查询使用说明\n\n{reason}\n\n"
|
||||
"请一次发送一个完整 eRob 型号(含版本)和一个正整数数量。\n"
|
||||
"提问示例:\neRob110H160I-FHM-18ET[V6] 10台\n\n"
|
||||
"也可以说:请查询 eRob110H160I-FHM-18ET[V6],数量10台的交期。\n"
|
||||
"请将示例替换为实际型号和数量;多个型号请分条发送。\n"
|
||||
"收到结果后,可在同一聊天中发送“还有没有其他候选型号”。\n\n"
|
||||
f"{DISCLAIMER}"
|
||||
)
|
||||
|
||||
|
||||
def handle_message(client: lark.Client, data: lark.im.v1.P2ImMessageReceiveV1) -> None:
|
||||
text = message_text(data)
|
||||
message_id = data.event.message.message_id
|
||||
if not text or not message_id:
|
||||
if not message_id:
|
||||
return
|
||||
if not text:
|
||||
guidance = input_guidance(data, text)
|
||||
if guidance:
|
||||
reply(client, message_id, guidance)
|
||||
return
|
||||
|
||||
key = conversation_key(data)
|
||||
@@ -1118,6 +1141,10 @@ def handle_message(client: lark.Client, data: lark.im.v1.P2ImMessageReceiveV1) -
|
||||
request = continuation_state.request
|
||||
candidate_offset = continuation_state.next_offset
|
||||
if request is None:
|
||||
guidance = input_guidance(data, text)
|
||||
if guidance:
|
||||
reply(client, message_id, guidance)
|
||||
return
|
||||
logging.info(
|
||||
"Feishu message ignored: no complete AM516 request or active candidate continuation; message_id=%s",
|
||||
message_id,
|
||||
@@ -1173,7 +1200,7 @@ def main() -> None:
|
||||
message_id = data.event.message.message_id
|
||||
sender_type = data.event.sender.sender_type
|
||||
logging.info("Feishu event received: message_id=%s sender_type=%s", message_id, sender_type)
|
||||
if data.event.sender.sender_type != "user" or not is_allowed_sender(data):
|
||||
if data.event.sender.sender_type != "user":
|
||||
return
|
||||
if not message_id:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user