新增 LINE 官方账号对接的全套功能,包括: 1. 基础的 Bot 探测、会话解析、消息格式化能力 2. 富媒体消息模板、快速回复、卡片指令支持 3. Webhook 签名验证、重放防护、多账户路由管理 4. 消息发送、回复、分块传输、用户绑定管理 5. 交互式配置向导与诊断工具
224 lines
6.0 KiB
Python
224 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
def build_buttons_template(
|
|
title: str,
|
|
text: str,
|
|
actions: list[dict],
|
|
thumbnail_url: str | None = None,
|
|
alt_text: str = "Buttons Template",
|
|
) -> dict:
|
|
template: dict = {
|
|
"type": "buttons",
|
|
"title": title[:40] if title else None,
|
|
"text": text[:160],
|
|
"actions": actions[:4],
|
|
}
|
|
if thumbnail_url:
|
|
template["thumbnailImageUrl"] = thumbnail_url
|
|
|
|
return {
|
|
"type": "template",
|
|
"altText": alt_text[:400],
|
|
"template": template,
|
|
}
|
|
|
|
|
|
def build_confirm_template(
|
|
text: str,
|
|
actions: list[dict],
|
|
alt_text: str = "Confirm",
|
|
) -> dict:
|
|
return {
|
|
"type": "template",
|
|
"altText": alt_text[:400],
|
|
"template": {
|
|
"type": "confirm",
|
|
"text": text[:240],
|
|
"actions": actions[:2],
|
|
},
|
|
}
|
|
|
|
|
|
def build_carousel_template(columns: list[dict], alt_text: str = "Carousel") -> dict:
|
|
return {
|
|
"type": "template",
|
|
"altText": alt_text[:400],
|
|
"template": {
|
|
"type": "carousel",
|
|
"columns": columns[:10],
|
|
},
|
|
}
|
|
|
|
|
|
def build_image_carousel_template(columns: list[dict], alt_text: str = "Image Carousel") -> dict:
|
|
return {
|
|
"type": "template",
|
|
"altText": alt_text[:400],
|
|
"template": {
|
|
"type": "image_carousel",
|
|
"columns": columns[:10],
|
|
},
|
|
}
|
|
|
|
|
|
def build_image_carousel_column(
|
|
image_url: str,
|
|
action: dict | None = None,
|
|
) -> dict:
|
|
col: dict = {
|
|
"imageUrl": image_url,
|
|
"imageSize": "cover",
|
|
}
|
|
if action:
|
|
col["action"] = action
|
|
return col
|
|
|
|
|
|
def build_product_carousel(products: list[dict], alt_text: str = "Product Carousel") -> dict:
|
|
columns = []
|
|
for product in products[:10]:
|
|
column = {
|
|
"thumbnailImageUrl": product.get("image_url", ""),
|
|
"title": product.get("title", "")[:40],
|
|
"text": product.get("description", "")[:120],
|
|
"actions": [
|
|
{
|
|
"type": "uri",
|
|
"label": product.get("action_label", "View")[:20],
|
|
"uri": product.get("action_url", ""),
|
|
}
|
|
],
|
|
}
|
|
if product.get("price"):
|
|
column["defaultAction"] = {
|
|
"type": "uri",
|
|
"label": "View",
|
|
"uri": product.get("action_url", ""),
|
|
}
|
|
columns.append(column)
|
|
|
|
return build_carousel_template(columns, alt_text)
|
|
|
|
|
|
def build_link_menu(
|
|
title: str,
|
|
links: list[dict],
|
|
alt_text: str = "Link Menu",
|
|
) -> dict:
|
|
actions = []
|
|
for link in links[:4]:
|
|
actions.append(
|
|
{
|
|
"type": "uri",
|
|
"label": link.get("label", "")[:20],
|
|
"uri": link.get("uri", ""),
|
|
}
|
|
)
|
|
|
|
return (
|
|
{
|
|
"type": "template",
|
|
"altText": alt_text[:400],
|
|
"template": {
|
|
"type": "buttons",
|
|
"title": title[:40],
|
|
"text": "",
|
|
"actions": actions,
|
|
},
|
|
}
|
|
if actions
|
|
else {}
|
|
)
|
|
|
|
|
|
def build_yes_no_confirm(
|
|
question: str,
|
|
yes_label: str = "Yes",
|
|
no_label: str = "No",
|
|
alt_text: str = "Confirm",
|
|
) -> dict:
|
|
return build_confirm_template(
|
|
text=question,
|
|
actions=[
|
|
{"type": "message", "label": yes_label[:20], "text": "yes"},
|
|
{"type": "message", "label": no_label[:20], "text": "no"},
|
|
],
|
|
alt_text=alt_text,
|
|
)
|
|
|
|
|
|
def build_template_message_from_payload(payload: dict[str, Any]) -> dict | None:
|
|
tmpl_type = payload.get("type", "")
|
|
if tmpl_type == "buttons":
|
|
return build_buttons_template(
|
|
title=payload.get("title", ""),
|
|
text=payload.get("text", ""),
|
|
actions=payload.get("actions", []),
|
|
thumbnail_url=payload.get("thumbnail_url"),
|
|
alt_text=payload.get("alt_text", "Buttons Template"),
|
|
)
|
|
if tmpl_type == "confirm":
|
|
return build_confirm_template(
|
|
text=payload.get("text", ""),
|
|
actions=payload.get("actions", []),
|
|
alt_text=payload.get("alt_text", "Confirm"),
|
|
)
|
|
if tmpl_type == "carousel":
|
|
return build_carousel_template(
|
|
columns=payload.get("columns", []),
|
|
alt_text=payload.get("alt_text", "Carousel"),
|
|
)
|
|
if tmpl_type == "image_carousel":
|
|
return build_image_carousel_template(
|
|
columns=payload.get("columns", []),
|
|
alt_text=payload.get("alt_text", "Image Carousel"),
|
|
)
|
|
if tmpl_type == "product_carousel":
|
|
return build_product_carousel(
|
|
products=payload.get("products", []),
|
|
alt_text=payload.get("alt_text", "Product Carousel"),
|
|
)
|
|
if tmpl_type == "link_menu":
|
|
return build_link_menu(
|
|
title=payload.get("title", ""),
|
|
links=payload.get("links", []),
|
|
alt_text=payload.get("alt_text", "Link Menu"),
|
|
)
|
|
if tmpl_type == "yes_no":
|
|
return build_yes_no_confirm(
|
|
question=payload.get("question", ""),
|
|
yes_label=payload.get("yes_label", "Yes"),
|
|
no_label=payload.get("no_label", "No"),
|
|
alt_text=payload.get("alt_text", "Confirm"),
|
|
)
|
|
return None
|
|
|
|
|
|
def build_datetime_picker_action(
|
|
mode: str = "datetime",
|
|
label: str = "Select",
|
|
data: str = "",
|
|
initial: str | None = None,
|
|
max_value: str | None = None,
|
|
min_value: str | None = None,
|
|
) -> dict:
|
|
if mode not in ("date", "time", "datetime"):
|
|
mode = "datetime"
|
|
|
|
action: dict = {
|
|
"type": "datetimepicker",
|
|
"label": label[:20],
|
|
"data": data[:300],
|
|
"mode": mode,
|
|
}
|
|
if initial:
|
|
action["initial"] = initial
|
|
if max_value:
|
|
action["max"] = max_value
|
|
if min_value:
|
|
action["min"] = min_value
|
|
return action
|