新增 LINE 官方账号对接的全套功能,包括: 1. 基础的 Bot 探测、会话解析、消息格式化能力 2. 富媒体消息模板、快速回复、卡片指令支持 3. Webhook 签名验证、重放防护、多账户路由管理 4. 消息发送、回复、分块传输、用户绑定管理 5. 交互式配置向导与诊断工具
124 lines
4.6 KiB
Python
124 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
QUICK_REPLIES_RE = re.compile(r"\[\[quick_replies:\s*(.+?)\]\]")
|
|
CONFIRM_RE = re.compile(r"\[\[confirm:\s*(.+?)\|\s*(.+?)\|\s*(.+?)\]\]")
|
|
BUTTONS_RE = re.compile(r"\[\[buttons:\s*(.+?)\]\]")
|
|
LOCATION_RE = re.compile(r"\[\[location:\s*(.+?)\]\]")
|
|
|
|
_MAX_DIRECTIVES = 5
|
|
|
|
|
|
def parse_line_directives(text: str) -> list[dict] | None:
|
|
if not text or not text.strip():
|
|
return None
|
|
|
|
result: list[dict] = []
|
|
|
|
qr_match = QUICK_REPLIES_RE.search(text)
|
|
if qr_match and len(result) < _MAX_DIRECTIVES:
|
|
labels = [lbl.strip() for lbl in qr_match.group(1).split(",") if lbl.strip()]
|
|
main_text = QUICK_REPLIES_RE.sub("", text).strip()
|
|
items = [
|
|
{
|
|
"type": "action",
|
|
"action": {"type": "message", "label": label[:20], "text": label},
|
|
}
|
|
for label in labels[:13]
|
|
]
|
|
if items:
|
|
result.append(
|
|
{
|
|
"type": "text",
|
|
"text": main_text[:5000],
|
|
"quickReply": {"items": items},
|
|
}
|
|
)
|
|
|
|
cf_match = CONFIRM_RE.search(text)
|
|
if cf_match and len(result) < _MAX_DIRECTIVES:
|
|
question = cf_match.group(1).strip()
|
|
yes_label = cf_match.group(2).strip()
|
|
no_label = cf_match.group(3).strip()
|
|
main_text = CONFIRM_RE.sub("", text).strip()
|
|
if main_text and question:
|
|
result.append({"type": "text", "text": main_text[:5000]})
|
|
if question:
|
|
result.append(
|
|
{
|
|
"type": "template",
|
|
"altText": question[:400],
|
|
"template": {
|
|
"type": "confirm",
|
|
"text": question[:240],
|
|
"actions": [
|
|
{"type": "message", "label": yes_label[:20], "text": "yes"},
|
|
{"type": "message", "label": no_label[:20], "text": "no"},
|
|
],
|
|
},
|
|
}
|
|
)
|
|
|
|
btn_match = BUTTONS_RE.search(text)
|
|
if btn_match and len(result) < _MAX_DIRECTIVES:
|
|
parts = [p.strip() for p in btn_match.group(1).split("|")]
|
|
if len(parts) >= 1:
|
|
title = parts[0] if len(parts) > 0 else ""
|
|
description = parts[1] if len(parts) > 1 else ""
|
|
btn_defs = parts[2] if len(parts) > 2 else ""
|
|
actions = []
|
|
if btn_defs.strip():
|
|
for btn_def in btn_defs.split(",")[:4]:
|
|
btn_def = btn_def.strip()
|
|
if not btn_def:
|
|
continue
|
|
if ":" in btn_def:
|
|
b_label, b_data = btn_def.split(":", 1)
|
|
b_label, b_data = b_label.strip(), b_data.strip()
|
|
if b_data.startswith("https://"):
|
|
actions.append({"type": "uri", "label": b_label[:20], "uri": b_data})
|
|
else:
|
|
actions.append({"type": "message", "label": b_label[:20], "text": b_data})
|
|
|
|
main_text = BUTTONS_RE.sub("", text).strip()
|
|
if main_text:
|
|
result.append({"type": "text", "text": main_text[:5000]})
|
|
if title and actions:
|
|
result.append(
|
|
{
|
|
"type": "template",
|
|
"altText": title[:400],
|
|
"template": {
|
|
"type": "buttons",
|
|
"title": title[:40],
|
|
"text": description[:160],
|
|
"actions": actions,
|
|
},
|
|
}
|
|
)
|
|
|
|
loc_match = LOCATION_RE.search(text)
|
|
if loc_match and len(result) < _MAX_DIRECTIVES:
|
|
parts = [p.strip() for p in loc_match.group(1).split("|")]
|
|
if len(parts) >= 4:
|
|
title, address = parts[0], parts[1]
|
|
try:
|
|
lat, lon = float(parts[2]), float(parts[3])
|
|
except ValueError:
|
|
lat, lon = 0.0, 0.0
|
|
main_text = LOCATION_RE.sub("", text).strip()
|
|
if main_text:
|
|
result.append({"type": "text", "text": main_text[:5000]})
|
|
result.append(
|
|
{
|
|
"type": "location",
|
|
"title": title[:100],
|
|
"address": address[:100],
|
|
"latitude": lat,
|
|
"longitude": lon,
|
|
}
|
|
)
|
|
|
|
return result[:_MAX_DIRECTIVES] if result else None
|