|
@@ -9,6 +9,7 @@ import re
|
|
|
from contextlib import suppress
|
|
from contextlib import suppress
|
|
|
from datetime import UTC, datetime
|
|
from datetime import UTC, datetime
|
|
|
from typing import Any
|
|
from typing import Any
|
|
|
|
|
+from zoneinfo import ZoneInfo
|
|
|
|
|
|
|
|
from aiohttp import ClientError, ClientSession, ClientTimeout
|
|
from aiohttp import ClientError, ClientSession, ClientTimeout
|
|
|
|
|
|
|
@@ -66,6 +67,7 @@ BUSINESS_ALLOWED_UPDATES = [
|
|
|
"deleted_business_messages",
|
|
"deleted_business_messages",
|
|
|
]
|
|
]
|
|
|
BUSINESS_REPLY_WINDOW_SECONDS = 24 * 60 * 60
|
|
BUSINESS_REPLY_WINDOW_SECONDS = 24 * 60 * 60
|
|
|
|
|
+BEIJING_TIMEZONE = ZoneInfo("Asia/Shanghai")
|
|
|
|
|
|
|
|
|
|
|
|
|
class TelegramBotApiError(RuntimeError):
|
|
class TelegramBotApiError(RuntimeError):
|
|
@@ -108,24 +110,34 @@ class FeishuWebhookClient:
|
|
|
webhook_url, payload, signing_secret=signing_secret
|
|
webhook_url, payload, signing_secret=signing_secret
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- async def send_post(
|
|
|
|
|
|
|
+ async def send_card(
|
|
|
self,
|
|
self,
|
|
|
webhook_url: str,
|
|
webhook_url: str,
|
|
|
*,
|
|
*,
|
|
|
title: str,
|
|
title: str,
|
|
|
- content: list[list[dict[str, str]]],
|
|
|
|
|
|
|
+ lines: list[str],
|
|
|
|
|
+ link_url: str = "",
|
|
|
signing_secret: str = "",
|
|
signing_secret: str = "",
|
|
|
) -> None:
|
|
) -> None:
|
|
|
- payload: dict[str, Any] = {
|
|
|
|
|
- "msg_type": "post",
|
|
|
|
|
- "content": {
|
|
|
|
|
- "post": {
|
|
|
|
|
- "zh_cn": {
|
|
|
|
|
- "title": str(title)[:100],
|
|
|
|
|
- "content": content,
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ card: dict[str, Any] = {
|
|
|
|
|
+ "config": {"wide_screen_mode": True},
|
|
|
|
|
+ "header": {
|
|
|
|
|
+ "template": "blue",
|
|
|
|
|
+ "title": {"tag": "plain_text", "content": str(title)[:100]},
|
|
|
},
|
|
},
|
|
|
|
|
+ "elements": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "tag": "div",
|
|
|
|
|
+ "text": {"tag": "plain_text", "content": str(line)[:1000]},
|
|
|
|
|
+ }
|
|
|
|
|
+ for line in lines
|
|
|
|
|
+ ],
|
|
|
|
|
+ }
|
|
|
|
|
+ if link_url:
|
|
|
|
|
+ card["card_link"] = {"url": link_url}
|
|
|
|
|
+ payload: dict[str, Any] = {
|
|
|
|
|
+ "msg_type": "interactive",
|
|
|
|
|
+ "card": card,
|
|
|
}
|
|
}
|
|
|
await self._send_payload(
|
|
await self._send_payload(
|
|
|
webhook_url, payload, signing_secret=signing_secret
|
|
webhook_url, payload, signing_secret=signing_secret
|
|
@@ -1147,43 +1159,36 @@ class BusinessAssistantRuntime:
|
|
|
preview_text = str(
|
|
preview_text = str(
|
|
|
message.get("text") or message.get("caption") or ""
|
|
message.get("text") or message.get("caption") or ""
|
|
|
).strip()
|
|
).strip()
|
|
|
- content: list[list[dict[str, str]]] = [
|
|
|
|
|
- [{"tag": "text", "text": f"账号:{account_name}"}],
|
|
|
|
|
- [{"tag": "text", "text": "用户:"}],
|
|
|
|
|
- [{"tag": "text", "text": f"类型:{message_type}"}],
|
|
|
|
|
|
|
+ lines = [
|
|
|
|
|
+ f"账号:{account_name}",
|
|
|
|
|
+ f"用户:{user_name}",
|
|
|
|
|
+ f"类型:{message_type}",
|
|
|
]
|
|
]
|
|
|
telegram_username = str(sender.get("username") or "").strip().lstrip("@")
|
|
telegram_username = str(sender.get("username") or "").strip().lstrip("@")
|
|
|
- if re.fullmatch(r"[A-Za-z0-9_]{1,64}", telegram_username):
|
|
|
|
|
- content[1].append(
|
|
|
|
|
- {
|
|
|
|
|
- "tag": "a",
|
|
|
|
|
- "text": user_name,
|
|
|
|
|
- "href": f"https://t.me/{telegram_username}",
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- else:
|
|
|
|
|
- content[1].append({"tag": "text", "text": user_name})
|
|
|
|
|
|
|
+ telegram_chat_url = (
|
|
|
|
|
+ f"https://t.me/{telegram_username}"
|
|
|
|
|
+ if re.fullmatch(r"[A-Za-z0-9_]{1,64}", telegram_username)
|
|
|
|
|
+ else ""
|
|
|
|
|
+ )
|
|
|
if settings.get("feishu_message_preview_enabled"):
|
|
if settings.get("feishu_message_preview_enabled"):
|
|
|
- content.append(
|
|
|
|
|
- [
|
|
|
|
|
- {
|
|
|
|
|
- "tag": "text",
|
|
|
|
|
- "text": f"内容:{preview_text[:500] if preview_text else '[非文本消息]'}",
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ lines.append(
|
|
|
|
|
+ f"内容:{preview_text[:500] if preview_text else '[非文本消息]'}"
|
|
|
)
|
|
)
|
|
|
try:
|
|
try:
|
|
|
message_time = datetime.fromtimestamp(
|
|
message_time = datetime.fromtimestamp(
|
|
|
int(message.get("date") or 0), UTC
|
|
int(message.get("date") or 0), UTC
|
|
|
- ).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
|
|
|
|
|
+ ).astimezone(BEIJING_TIMEZONE).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
except (OSError, OverflowError, TypeError, ValueError):
|
|
except (OSError, OverflowError, TypeError, ValueError):
|
|
|
message_time = "未知"
|
|
message_time = "未知"
|
|
|
- content.append([{"tag": "text", "text": f"时间:{message_time}"}])
|
|
|
|
|
|
|
+ lines.append(f"时间(北京时间):{message_time}")
|
|
|
|
|
+ if telegram_chat_url:
|
|
|
|
|
+ lines.append("点击卡片打开 Telegram 对话")
|
|
|
try:
|
|
try:
|
|
|
- await self.feishu.send_post(
|
|
|
|
|
|
|
+ await self.feishu.send_card(
|
|
|
webhook_url,
|
|
webhook_url,
|
|
|
title="消息提醒",
|
|
title="消息提醒",
|
|
|
- content=content,
|
|
|
|
|
|
|
+ lines=lines,
|
|
|
|
|
+ link_url=telegram_chat_url,
|
|
|
signing_secret=str(settings.get("feishu_webhook_signing_secret") or ""),
|
|
signing_secret=str(settings.get("feishu_webhook_signing_secret") or ""),
|
|
|
)
|
|
)
|
|
|
except FeishuWebhookError as exc:
|
|
except FeishuWebhookError as exc:
|
|
@@ -1222,15 +1227,10 @@ class BusinessAssistantRuntime:
|
|
|
for item in (account.get("first_name"), account.get("last_name"))
|
|
for item in (account.get("first_name"), account.get("last_name"))
|
|
|
if str(item or "").strip()
|
|
if str(item or "").strip()
|
|
|
) or (f"@{account.get('username')}" if account.get("username") else "Business 账号")
|
|
) or (f"@{account.get('username')}" if account.get("username") else "Business 账号")
|
|
|
- await self.feishu.send(
|
|
|
|
|
|
|
+ await self.feishu.send_card(
|
|
|
webhook_url,
|
|
webhook_url,
|
|
|
- "\n".join(
|
|
|
|
|
- [
|
|
|
|
|
- "消息提醒",
|
|
|
|
|
- "飞书群机器人连接测试成功。",
|
|
|
|
|
- f"账号:{account_name}",
|
|
|
|
|
- ]
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ title="消息提醒",
|
|
|
|
|
+ lines=["飞书群机器人连接测试成功。", f"账号:{account_name}"],
|
|
|
signing_secret=str(settings.get("feishu_webhook_signing_secret") or ""),
|
|
signing_secret=str(settings.get("feishu_webhook_signing_secret") or ""),
|
|
|
)
|
|
)
|
|
|
return {"ok": True, "account": account_name}
|
|
return {"ok": True, "account": account_name}
|